update bug

This commit is contained in:
靳中伟 2025-05-12 15:43:21 +08:00
parent 0189b1f0b9
commit b684132a73
87 changed files with 484659 additions and 444885 deletions

6
.idea/vcs.xml generated Normal file
View 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>

View File

@ -83,8 +83,8 @@
|-------|------|-----|------|
| label | String | 是 | 任务名称 |
| taskType | Integer | 是 | 任务类型1-普通任务2-定时任务 |
| mapId | Integer | 是 | 相关地图ID|
| remark | String | 否 | 任务备注 |
| mapId | String | 是 | 相关地图ID|
| remark | Integer | 否 | 任务备注 |
| period | Integer | 否 | 周期时间(毫秒) |
| delay | Integer | 否 | 延迟时间毫秒默认3000 |
| releaseSites | Boolean | 否 | 是否释放站点默认true |
@ -355,3 +355,77 @@ X-Content-Type-Options: nosniff
"data": null
}
```
### 9. 停止任务定义下的所有任务实例
#### 接口描述
停止指定任务定义下的所有正在运行的任务实例,同时如果该任务是定时任务,则会禁用定时任务功能。
#### 请求方式
- **HTTP方法**: POST
- **接口路径**: `/api/vwed-task/stop-all/{task_def_id}`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|-------|------|-----|------|
| task_def_id | String | 是 | 任务定义ID路径参数 |
#### 响应参数
```json
{
"code": 200,
"message": "已禁用定时任务并停止了 2/2 个运行中的任务实例",
"data": {
"taskDefId": "193f8412-fa0d-4b6d-9648-70bceacd6629",
"isPeriodic": true,
"totalRunning": 2,
"stoppedCount": 2,
"failedCount": 0,
"failedTasks": []
}
}
```
#### 部分成功响应示例
```json
{
"code": 200,
"message": "已禁用定时任务并停止了 1/2 个运行中的任务实例",
"data": {
"taskDefId": "193f8412-fa0d-4b6d-9648-70bceacd6629",
"isPeriodic": true,
"totalRunning": 2,
"stoppedCount": 1,
"failedCount": 1,
"failedTasks": [
{
"id": "task-instance-001",
"reason": "任务实例已处于终止状态"
}
]
}
}
```
#### 错误响应示例
```json
{
"code": 400,
"message": "停止任务失败: 任务定义不存在",
"data": null
}
```
```json
{
"code": 500,
"message": "停止任务失败: 内部服务器错误",
"data": null
}
```

View File

@ -722,77 +722,6 @@
}
```
### 6. 停止所有相关任务
#### 接口描述
停止指定任务定义下的所有正在运行的任务实例,同时如果是定时任务,则禁用定时任务。此接口对于批量终止任务非常有用,尤其是当需要紧急停止某个任务的所有运行实例时。
#### 请求方式
- **HTTP方法**: POST
- **接口路径**: `/api/vwed-task-edit/stop-all/{task_def_id}`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|-------|------|-----|------|
| task_def_id | String | 是 | 任务定义ID路径参数不是任务记录ID |
#### 响应参数
```json
{
"code": 200,
"message": "已禁用定时任务并停止了 3/3 个运行中的任务实例",
"data": {
"taskDefId": "193f8412-fa0d-4b6d-9648-70bceacd6629",
"isPeriodic": true,
"totalRunning": 3,
"stoppedCount": 3,
"failedCount": 0,
"failedTasks": []
}
}
```
#### 错误响应
1. 任务不存在时:
```json
{
"code": 400,
"message": "未找到任务定义: 193f8412-fa0d-4b6d-9648-70bceacd6629"
}
```
2. 停止失败时:
```json
{
"code": 400,
"message": "停止任务失败: [详细错误信息]"
}
```
3. 部分任务停止失败时:
```json
{
"code": 200,
"message": "已禁用定时任务并停止了 2/3 个运行中的任务实例",
"data": {
"taskDefId": "193f8412-fa0d-4b6d-9648-70bceacd6629",
"isPeriodic": true,
"totalRunning": 3,
"stoppedCount": 2,
"failedCount": 1,
"failedTasks": [
{
"taskRecordId": "a52c6b91-de78-4fd2-9d18-5f79ba8e7c1d",
"reason": "任务状态已变更,无法停止"
}
]
}
}
```
### 8. 运行任务
#### 接口描述

View File

@ -0,0 +1,698 @@
# VWED任务记录接口文档
本文档描述了VWED系统任务运行记录相关的API接口主要用于查询任务执行过程中的块运行情况和详情。
## 基础信息
- 基础路径:`/api/vwed-task-record`
- 接口标签:`VWED任务运行记录`
## 接口清单
| 序号 | 接口名称 | 接口路径 | 请求方式 | 接口描述 |
| --- | --- | --- | --- | --- |
| 1 | 获取任务块运行记录 | `/blocks/{task_record_id}` | GET | 获取指定任务记录下的所有块运行情况 |
| 2 | 获取块记录详情 | `/block/{block_record_id}` | GET | 获取指定块记录的详细信息 |
| 3 | 终止任务 | `/stop/{task_record_id}` | POST | 停止指定任务记录下的所有运行任务实例 |
| 4 | 获取任务执行结果 | `/execution/block/results/{task_record_id}` | GET | 获取指定任务记录的执行结果 |
| 5 | 获取任务记录详情 | `/detail/{task_record_id}` | GET | 获取指定任务记录的详细信息 |
## 接口详情
### 1. 获取任务块运行记录
#### 接口说明
获取指定任务记录下的所有块运行情况,包括每个块的运行状态、执行时间、输入输出参数等信息。
#### 请求路径
```
GET /api/vwed-task-record/blocks/{task_record_id}
```
#### 路径参数
| 参数名 | 类型 | 是否必须 | 描述 |
| --- | --- | --- | --- |
| task_record_id | string | 是 | 任务记录ID由run_task接口返回的taskRecordId |
#### 响应结果
成功响应:
```json
{
"code": 200,
"message": "成功获取任务记录 {task_record_id} 的块运行情况",
"data": [
{
"id": "块记录ID",
"block_name": "块名称",
"block_id": "块ID",
"status": 块执行状态码,
"started_on": "开始时间",
"ended_on": "结束时间",
"ended_reason": "结束原因",
"block_execute_name": "块执行名称",
"block_input_params_value": {
"参数名": "参数值"
},
"block_out_params_value": {
"参数名": "参数值"
},
"remark": "备注"
}
]
}
```
失败响应:
```json
{
"code": 500,
"message": "获取任务块运行情况失败: {错误信息}"
}
```
#### 响应字段说明
| 字段名 | 类型 | 描述 |
| --- | --- | --- |
| id | string | 块记录ID |
| block_name | string | 块名称 |
| block_id | string | 块ID |
| status | integer | 块执行状态1000:执行成功1001:执行中1002:未执行2000:执行失败2002:取消 |
| started_on | string | 开始时间ISO 8601格式 |
| ended_on | string | 结束时间ISO 8601格式 |
| ended_reason | string | 结束原因 |
| block_execute_name | string | 块执行名称 |
| block_input_params_value | object | 块输入参数值 |
| block_out_params_value | object | 块输出参数值 |
| remark | string | 备注 |
#### 示例
请求示例:
```
GET /api/vwed-task-record/blocks/7b84fd4d-e947-4ec7-8535-05ede5f8aa9d
```
响应示例:
```json
{
"code": 200,
"message": "成功获取任务记录 7b84fd4d-e947-4ec7-8535-05ede5f8aa9d 的块运行情况",
"data": [
{
"id": "b5410c81-99b3-4d61-bb79-e54292d012d4",
"block_name": "-1",
"block_id": "-1",
"status": 1000,
"started_on": "2025-04-30T17:28:19.105648",
"ended_on": "2025-04-30T17:28:19.601597",
"ended_reason": "执行成功",
"block_execute_name": "RootBp",
"block_input_params_value": {},
"block_out_params_value": null,
"remark": "执行成功"
},
{
"id": "ee29eccd-11e8-490b-ac24-81f243771dda",
"block_name": "b1",
"block_id": "1",
"status": 1000,
"started_on": "2025-04-30T17:28:19.149185",
"ended_on": "2025-04-30T17:28:19.576944",
"ended_reason": "执行成功",
"block_execute_name": "IterateListBp",
"block_input_params_value": {
"list": "[1,2,3,4,5]"
},
"block_out_params_value": {
"index": 4,
"item": 5
},
"remark": "执行成功"
},
{
"id": "c944c7df-329c-468e-90ed-9e064ddccdbc",
"block_name": "b2",
"block_id": "2",
"status": 1000,
"started_on": "2025-04-30T17:28:19.457166",
"ended_on": "2025-04-30T17:28:19.488009",
"ended_reason": "执行成功",
"block_execute_name": "PrintBp",
"block_input_params_value": {
"message": "blocks.b1.item"
},
"block_out_params_value": null,
"remark": "执行成功"
}
]
}
```
### 2. 获取块记录详情
#### 接口说明
获取指定块记录的详细信息,包括完整的输入输出参数、内部变量等数据。
#### 请求路径
```
GET /api/vwed-task-record/block/{block_record_id}
```
#### 路径参数
| 参数名 | 类型 | 是否必须 | 描述 |
| --- | --- | --- | --- |
| block_record_id | string | 是 | 块记录ID |
#### 响应结果
成功响应:
```json
{
"code": 200,
"message": "成功获取块记录详情",
"data": {
"id": "块记录ID",
"block_name": "块名称",
"block_id": "块ID",
"block_config_id": "块配置ID",
"block_input_params": {
"参数名": {
"type": "参数类型",
"value": "参数默认值",
"required": true/false
}
},
"block_input_params_value": {
"参数名": "参数值"
},
"block_out_params_value": {
"参数名": "参数值"
},
"block_internal_variables": {
"变量名": "变量值"
},
"block_execute_name": "块执行名称",
"task_id": "任务定义ID",
"task_record_id": "任务记录ID",
"started_on": "开始时间",
"ended_on": "结束时间",
"ended_reason": "结束原因",
"status": 块执行状态码,
"ctrl_status": 控制状态码,
"input_params": {
"参数名": {
"type": "参数类型",
"value": "参数默认值",
"required": true/false
}
},
"internal_variables": {
"变量名": "变量值"
},
"output_params": {
"参数名": "参数值"
},
"version": 版本号,
"remark": "备注"
}
}
```
失败响应:
```json
{
"code": 404,
"message": "未找到ID为 {block_record_id} 的块记录"
}
```
#### 响应字段说明
| 字段名 | 类型 | 描述 |
| --- | --- | --- |
| id | string | 块记录ID |
| block_name | string | 块名称 |
| block_id | string | 块ID |
| block_config_id | string | 块配置ID |
| block_input_params | object | 块输入参数定义 |
| block_input_params_value | object | 块输入参数值 |
| block_out_params_value | object | 块输出参数值 |
| block_internal_variables | object | 块内部变量 |
| block_execute_name | string | 块执行名称 |
| task_id | string | 任务定义ID |
| task_record_id | string | 任务记录ID |
| started_on | string | 开始时间ISO 8601格式 |
| ended_on | string | 结束时间ISO 8601格式 |
| ended_reason | string | 结束原因 |
| status | integer | 块执行状态1000:执行成功1001:执行中1002:未执行2000:执行失败2002:取消 |
| ctrl_status | integer | 控制状态 |
| input_params | object | 输入参数 |
| internal_variables | object | 内部变量 |
| output_params | object | 输出参数 |
| version | integer | 版本号 |
| remark | string | 备注 |
#### 示例
请求示例:
```
GET /api/vwed-task-record/block/00b7dc0d-7c23-4dce-ae95-3618f2cad202
```
响应示例:
```json
{
"code": 200,
"message": "成功获取块记录详情",
"data": {
"id": "00b7dc0d-7c23-4dce-ae95-3618f2cad202",
"block_name": "b1",
"block_id": "1",
"block_config_id": "",
"block_input_params": {
"keyRoute": {
"type": "Simple",
"value": "TK01",
"required": true
}
},
"block_input_params_value": {
"keyRoute": "TK01"
},
"block_out_params_value": null,
"block_internal_variables": {},
"block_execute_name": "CSelectAgvBp",
"task_id": "3273c7cb-b4bb-47df-9d47-17f96bc401fc",
"task_record_id": "5ce20794-2b45-4031-8de0-9df922f91bd1",
"started_on": "2025-04-30T08:36:37.847431",
"ended_on": "2025-04-30T08:36:37.959064",
"ended_reason": "选择执行机器人失败: 执行数据库异常,违反了完整性例如:违反惟一约束、违反非空限制、字段内容超出长度等",
"status": 2000,
"ctrl_status": null,
"input_params": {
"keyRoute": {
"type": "Simple",
"value": "TK01",
"required": true
}
},
"internal_variables": {},
"output_params": {
"blocks": {
"b1": null
}
},
"version": 1,
"remark": "选择执行机器人失败: 执行数据库异常,违反了完整性例如:违反惟一约束、违反非空限制、字段内容超出长度等"
}
}
```
### 3. 终止任务
#### 接口说明
停止指定任务记录下的所有运行任务实例,同时禁用定时任务。
#### 请求路径
```
POST /api/vwed-task-record/stop/{task_record_id}
```
#### 路径参数
| 参数名 | 类型 | 是否必须 | 描述 |
| --- | --- | --- | --- |
| task_record_id | string | 是 | 任务记录ID |
#### 响应结果
成功响应:
```json
{
"code": 200,
"message": "任务终止成功",
"data": {
"task_record_id": "任务记录ID",
"status": 任务状态码,
"ended_on": "结束时间",
"ended_reason": "结束原因"
}
}
```
失败响应:
```json
{
"code": 400,
"message": "任务终止失败: {错误信息}"
}
```
```json
{
"code": 500,
"message": "任务记录终止失败: {错误信息}"
}
```
#### 响应字段说明
| 字段名 | 类型 | 描述 |
| --- | --- | --- |
| task_record_id | string | 任务记录ID |
| status | integer | 任务状态码 |
| ended_on | string | 结束时间ISO 8601格式 |
| ended_reason | string | 结束原因 |
#### 示例
请求示例:
```
POST /api/vwed-task-record/stop/7b84fd4d-e947-4ec7-8535-05ede5f8aa9d
```
响应示例:
```json
{
"code": 200,
"message": "任务终止成功",
"data": {
"task_record_id": "7b84fd4d-e947-4ec7-8535-05ede5f8aa9d",
"status": 2002,
"ended_on": "2025-04-30T17:35:42.105648",
"ended_reason": "任务终止"
}
}
```
### 4. 获取任务执行结果
#### 接口说明
获取指定任务记录的执行结果,包括任务的输出参数和执行状态等信息。
#### 请求路径
```
GET /api/vwed-task-record/execution/block/results/{task_record_id}
```
#### 路径参数
| 参数名 | 类型 | 是否必须 | 描述 |
| --- | --- | --- | --- |
| task_record_id | string | 是 | 任务记录ID |
#### 响应结果
成功响应:
```json
{
"code": 200,
"message": "成功获取任务记录执行结果",
"data": [
{
"created_at": "2025-05-09T10:17:48.447047",
"context": "[CacheDataBp] 数据缓存成功: test",
"status": 1000
},
{
"created_at": "2025-05-09T10:17:48.407731",
"context": "[RootBp] c1973f22-b098-47f7-80ae-97e9df1644ae",
"status": 1000
},
{
"created_at": "2025-05-09T10:17:48.611951",
"context": "[PrintBp] 打印成功@{'cache_data': 1}",
"status": 1000
},
{
"created_at": "2025-05-09T10:17:48.518772",
"context": "[GetCacheDataBp] 获取缓存数据成功: test",
"status": 1000
}
]
}
```
失败响应:
```json
{
"code": 404,
"message": "未找到ID为 {task_record_id} 的任务记录"
}
```
```json
{
"code": 500,
"message": "获取任务记录执行结果失败: {错误信息}"
}
```
#### 响应字段说明
| 字段名 | 类型 | 描述 |
| --- | --- | --- |
| created_at | string | 记录创建时间ISO 8601格式 |
| context | string | 执行上下文信息,包含块名称和执行结果 |
| status | integer | 执行状态码1000:执行成功1001:执行中1002:未执行2000:执行失败2002:取消 |
#### 示例
请求示例:
```
GET /api/vwed-task-record/execution/block/results/7b84fd4d-e947-4ec7-8535-05ede5f8aa9d
```
响应示例:
```json
{
"code": 200,
"message": "成功获取任务记录执行结果",
"data": [
{
"created_at": "2025-05-09T10:17:48.447047",
"context": "[CacheDataBp] 数据缓存成功: test",
"status": 1000
},
{
"created_at": "2025-05-09T10:17:48.407731",
"context": "[RootBp] c1973f22-b098-47f7-80ae-97e9df1644ae",
"status": 1000
},
{
"created_at": "2025-05-09T10:17:48.611951",
"context": "[PrintBp] 打印成功@{'cache_data': 1}",
"status": 1000
},
{
"created_at": "2025-05-09T10:17:48.518772",
"context": "[GetCacheDataBp] 获取缓存数据成功: test",
"status": 1000
}
]
}
```
### 5. 获取任务记录详情
#### 接口说明
获取指定任务记录的详细信息,包括任务的基本信息、执行状态、开始结束时间等。
#### 请求路径
```
GET /api/vwed-task-record/detail/{task_record_id}
```
#### 路径参数
| 参数名 | 类型 | 是否必须 | 描述 |
| --- | --- | --- | --- |
| task_record_id | string | 是 | 任务记录ID |
#### 响应结果
成功响应:
```json
{
"code": 200,
"message": "成功获取任务记录详情",
"data": {
"id": "任务记录ID",
"task_id": "任务定义ID",
"task_name": "任务名称",
"task_version": 任务版本,
"status": 任务状态码,
"input_params": {
"参数名": "参数值"
},
"started_on": "开始时间",
"ended_on": "结束时间",
"ended_reason": "结束原因",
"execution_time": 执行时长(毫秒),
"created_at": "创建时间",
"updated_at": "更新时间",
"agv_id": "执行任务的AGV设备ID",
"parent_task_record_id": "父任务记录ID",
"root_task_record_id": "根任务记录ID",
"state_description": "状态描述",
"if_have_child_task": 是否有子任务,
"periodic_task": 是否为周期任务,
"priority": 优先级,
"work_stations": "工作站",
"work_types": "工作类型",
"variables": {
"变量名": "变量值"
},
"source_type": 任务来源类型,
"source_system": "来源系统标识",
"source_user": "下达任务的用户ID或账号",
"source_device": "下达任务的硬件设备标识",
"source_ip": "下达任务的IP地址",
"source_time": "任务下达时间",
"source_client_info": "客户端设备信息",
"source_remarks": "任务来源备注信息"
}
}
```
失败响应:
```json
{
"code": 404,
"message": "未找到ID为 {task_record_id} 的任务记录"
}
```
```json
{
"code": 500,
"message": "获取任务记录详情失败: {错误信息}"
}
```
#### 响应字段说明
| 字段名 | 类型 | 描述 |
| --- | --- | --- |
| id | string | 任务记录ID |
| task_id | string | 任务定义ID对应def_id字段 |
| task_name | string | 任务名称对应def_label字段 |
| task_version | integer | 任务定义版本对应def_version字段 |
| status | integer | 任务状态码1000:执行成功1001:执行中1002:队列中2000:失败2001:取消2002:暂停 |
| input_params | object | 输入参数JSON格式 |
| started_on | string | 开始时间对应first_executor_time字段ISO 8601格式 |
| ended_on | string | 结束时间ISO 8601格式 |
| ended_reason | string | 结束原因 |
| execution_time | integer | 执行时长(毫秒)根据开始和结束时间计算或executor_time字段值 |
| created_at | string | 创建时间ISO 8601格式 |
| updated_at | string | 更新时间ISO 8601格式 |
| agv_id | string | 执行任务的AGV设备ID |
| parent_task_record_id | string | 父任务记录ID |
| root_task_record_id | string | 根任务记录ID |
| state_description | string | 状态描述 |
| if_have_child_task | boolean | 是否有子任务 |
| periodic_task | integer | 是否为周期任务0:否1:是 |
| priority | integer | 优先级,数值越大优先级越高 |
| work_stations | string | 工作站 |
| work_types | string | 工作类型 |
| variables | object | 变量信息JSON格式 |
| source_type | integer | 任务来源类型1:系统调度2:呼叫机3:第三方系统4:手持电脑) |
| source_system | string | 来源系统标识WMS、MES等系统编号 |
| source_user | string | 下达任务的用户ID或账号 |
| source_device | string | 下达任务的硬件设备标识设备ID、MAC地址等 |
| source_ip | string | 下达任务的IP地址 |
| source_time | string | 任务下达时间ISO 8601格式 |
| source_client_info | string | 客户端设备信息(用户代理、浏览器、操作系统等) |
| source_remarks | string | 任务来源备注信息 |
#### 示例
请求示例:
```
GET /api/vwed-task-record/detail/7b84fd4d-e947-4ec7-8535-05ede5f8aa9d
```
响应示例:
```json
{
"code": 200,
"message": "成功获取任务记录详情",
"data": {
"id": "7b84fd4d-e947-4ec7-8535-05ede5f8aa9d",
"task_id": "3273c7cb-b4bb-47df-9d47-17f96bc401fc",
"task_name": "示例任务",
"task_version": 2,
"status": 1000,
"input_params": {
"from": "A",
"to": "B"
},
"started_on": "2025-04-30T17:28:19.105648",
"ended_on": "2025-04-30T17:28:20.601597",
"ended_reason": "执行成功",
"execution_time": 1496,
"created_at": "2025-04-30T17:28:19.100000",
"updated_at": "2025-04-30T17:28:20.610000",
"agv_id": "AGV001",
"parent_task_record_id": null,
"root_task_record_id": "7b84fd4d-e947-4ec7-8535-05ede5f8aa9d",
"state_description": "任务已完成",
"if_have_child_task": false,
"periodic_task": 0,
"priority": 1,
"work_stations": "WS001",
"work_types": "运输任务",
"variables": {
"position": {"x": 100, "y": 200}
},
"source_type": 1,
"source_system": "WMS",
"source_user": "admin",
"source_device": "PC-001",
"source_ip": "192.168.1.100",
"source_time": "2025-04-30T17:28:18.000000",
"source_client_info": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"source_remarks": "测试任务"
}
}
```

View File

@ -8,7 +8,7 @@
### 2.1 核心任务定义和执行相关表
#### t_windtaskdef任务定义表
#### vwed_taskdef任务定义表
- **功能**:定义任务模板,包含任务流程和逻辑
- **主要字段**
- `id`: VARCHAR(255) NOT NULL PRIMARY KEY - 任务定义唯一标识
@ -26,19 +26,23 @@
- `create_date`: DATETIME - 创建日期
- `remark`: VARCHAR(255) - 备注
- `tenant_id`: VARCHAR(255) NOT NULL - 租户ID用于多租户隔离
- `map_id`: VARCHAR(255) NOT NULL - 地图ID
- `user_token`: VARCHAR(500) - 用户token值每次请求需要用到的认证信息
- `created_at`: DATETIME - 创建时间
- `updated_at`: DATETIME - 更新时间
- `is_deleted`: DATETIME - 是否删除(软删除标记)
#### t_windtaskrecord任务执行记录表
#### vwed_taskrecord任务执行记录表
- **功能**:记录任务的执行情况和结果
- **主要字段**
- `id`: VARCHAR(255) NOT NULL PRIMARY KEY - 记录ID
- `def_id`: VARCHAR(255) - 对应的任务定义ID
- `def_label`: VARCHAR(150) - 任务标签
- `def_version`: INT - 任务定义版本
- `created_on`: DATETIME - 创建时间
- `ended_on`: DATETIME - 结束时间
- `ended_reason`: LONGTEXT - 结束原因
- `status`: INT - 任务状态1001: 进行中, 1003: 已完成, 1006: 失败
- `status`: INT - 任务状态1000: 运行成功, 1001: 进行中, 1002: 队列中, 2000: 失败, 2001: 取消, 2002: 暂停
- `input_params`: LONGTEXT - 输入参数
- `path`: TEXT - 执行路径记录AGV移动路径
- `agv_id`: VARCHAR(150) - 执行任务的AGV设备ID
@ -57,19 +61,33 @@
- `variables`: LONGTEXT - 变量信息
- `call_work_station`: VARCHAR(255) - 调用工作站
- `call_work_type`: VARCHAR(255) - 调用工作类型
- `source_type`: INT NOT NULL - 任务来源类型1: 系统调度, 2: 呼叫机, 3: 第三方系统, 4: 手持电脑)
- `source_system`: VARCHAR(100) NOT NULL - 来源系统标识WMS、MES等系统编号
- `source_user`: VARCHAR(100) - 下达任务的用户ID或账号
- `source_device`: VARCHAR(255) NOT NULL - 下达任务的硬件设备标识设备ID、MAC地址等
- `source_ip`: VARCHAR(50) - 下达任务的IP地址
- `source_time`: DATETIME NOT NULL - 任务下达时间
- `source_client_info`: TEXT - 客户端设备信息(用户代理、浏览器、操作系统等)
- `source_remarks`: TEXT - 任务来源备注信息
- `allow_restart_same_location`: BOOLEAN DEFAULT FALSE - 运行状态时相同地址是否可再次启动该任务
- `created_at`: DATETIME - 创建时间
- `updated_at`: DATETIME - 更新时间
- `is_deleted`: DATETIME - 是否删除(软删除标记)
#### t_windtasklog任务日志表
#### vwed_tasklog任务日志表
- **功能**:记录任务执行过程中的详细日志信息,用于监控和调试
- **主要字段**
- `id`: VARCHAR(255) NOT NULL PRIMARY KEY - 日志记录ID
- `create_time`: DATETIME - 日志创建时间
- `level`: INT - 日志级别1: 信息, 3: 错误等)
- `message`: LONGTEXT - 日志消息内容
- `task_block_id`: INT - 任务块ID
- `task_id`: VARCHAR(255) - 对应的任务定义ID
- `task_record_id`: VARCHAR(255) - 对应的任务执行记录ID
- `created_at`: DATETIME - 创建时间
- `updated_at`: DATETIME - 更新时间
- `is_deleted`: DATETIME - 是否删除(软删除标记)
#### t_windblockrecord任务块执行记录表
#### vwed_blockrecord任务块执行记录表
- **功能**:记录任务块的执行情况,一个任务由多个任务块组成
- **主要字段**
- `id`: VARCHAR(255) NOT NULL PRIMARY KEY - 记录ID
@ -92,10 +110,13 @@
- `output_params`: LONGTEXT - 输出参数
- `version`: INT - 版本号
- `remark`: LONGTEXT - 备注
- `created_at`: DATETIME - 创建时间
- `updated_at`: DATETIME - 更新时间
- `is_deleted`: DATETIME - 是否删除(软删除标记)
### 2.2 任务模板和配置相关表
#### t_windtasktemplate任务模板表
#### vwed_tasktemplate任务模板表
- **功能**:定义可用的任务模板类型
- **主要字段**
- `id`: VARCHAR(255) NOT NULL PRIMARY KEY - 模板ID
@ -103,25 +124,33 @@
- `template_description`: VARCHAR(255) NOT NULL - 模板描述
- `template_if_enable`: INT NOT NULL DEFAULT 0 - 是否启用
- `template_dir`: VARCHAR(255) - 模板目录
- `created_at`: DATETIME - 创建时间
- `updated_at`: DATETIME - 更新时间
- `is_deleted`: DATETIME - 是否删除(软删除标记)
### 2.3 数据缓存相关表
#### t_winddatacache数据缓存表
#### vwed_datacache数据缓存表
- **功能**:存储系统配置和临时数据
- **主要字段**
- `id`: VARCHAR(255) NOT NULL PRIMARY KEY - 缓存ID
- `data`: LONGTEXT - 缓存数据JSON格式
- `created_at`: DATETIME - 创建时间
- `updated_at`: DATETIME - 更新时间
- `is_deleted`: DATETIME - 是否删除(软删除标记)
#### t_winddatacachesplit用户数据缓存表
#### vwed_datacachesplit用户数据缓存表
- **功能**:存储用户特定的缓存数据,支持更高效的数据索引和检索
- **主要字段**
- `id`: VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL PRIMARY KEY - 缓存记录ID
- `data_key`: LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL - 数据键名
- `data_value`: LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL - 数据值
- `updated_on`: DATETIME NULL - 更新时间
- `created_at`: DATETIME - 创建时间
- `updated_at`: DATETIME - 更新时间
- `is_deleted`: DATETIME - 是否删除(软删除标记)
- INDEX `dataKeyIndex`(`data_key`(768) ASC) USING BTREE - 数据键名索引,提高查询效率
#### t_modbus_configModbus配置表
#### modbus_configModbus配置表
- **功能**存储Modbus通信配置参数用于PLC等设备的通信连接和数据交换
- **主要字段**
- `id`: VARCHAR(255) NOT NULL PRIMARY KEY - 配置记录ID
@ -134,14 +163,15 @@
- `task_id`: VARCHAR(255) - 天风任务id
- `target_value`: INT - 目标值
- `remark`: VARCHAR(255) - 备注(如"1为货物到位0为空"
- `create_date`: DATETIME - 创建时间
- `update_date`: DATETIME - 更新时间
- `status`: INT DEFAULT 0 - 状态1:启用, 0:禁用)
- `tenant_id`: VARCHAR(255) - 租户ID
- `created_at`: DATETIME - 创建时间
- `updated_at`: DATETIME - 更新时间
- `is_deleted`: DATETIME - 是否删除(软删除标记)
### 2.4 在线脚本相关表
#### t_wind_script在线脚本表
#### vwed_script在线脚本表
- **功能**存储系统中的Python脚本
- **主要字段**
- `id`: VARCHAR(255) NOT NULL PRIMARY KEY - 脚本唯一标识
@ -155,13 +185,15 @@
- `is_public`: BIT(1) NOT NULL DEFAULT 1 - 是否公开
- `tags`: VARCHAR(255) - 标签,用于分类查询
- `created_by`: VARCHAR(255) - 创建者
- `created_on`: DATETIME - 创建时间
- `updated_by`: VARCHAR(255) - 最后更新者
- `updated_on`: DATETIME - 最后更新时间
- `test_params`: LONGTEXT - 测试参数(JSON格式)
- `created_at`: DATETIME - 创建时间
- `updated_at`: DATETIME - 更新时间
- `is_deleted`: DATETIME - 是否删除(软删除标记)
- UNIQUE KEY `uk_folder_filename` (`folder_path`, `file_name`) - 确保同目录下文件名唯一
#### t_wind_script_version脚本版本表
#### vwed_script_version脚本版本表
- **功能**:保存脚本历史版本
- **主要字段**
- `id`: VARCHAR(255) NOT NULL PRIMARY KEY - 版本记录ID
@ -170,10 +202,12 @@
- `code`: LONGTEXT NOT NULL - 该版本代码
- `change_log`: TEXT - 版本变更说明
- `created_by`: VARCHAR(255) - 创建者
- `created_on`: DATETIME - 创建时间
- `created_at`: DATETIME - 创建时间
- `updated_at`: DATETIME - 更新时间
- `is_deleted`: DATETIME - 是否删除(软删除标记)
- UNIQUE KEY `uk_script_version` (`script_id`, `version`) - 确保脚本版本唯一
#### t_wind_script_log脚本执行日志表
#### vwed_script_log脚本执行日志表
- **功能**:记录脚本执行情况
- **主要字段**
- `id`: VARCHAR(255) NOT NULL PRIMARY KEY - 日志ID
@ -188,10 +222,14 @@
- `execution_time`: INT - 执行耗时(毫秒)
- `started_on`: DATETIME - 开始时间
- `ended_on`: DATETIME - 结束时间
- `created_at`: DATETIME - 创建时间
- `updated_at`: DATETIME - 更新时间
- `is_deleted`: DATETIME - 是否删除(软删除标记)
### 2.5 接口定义相关表
#### t_interfacedefhistory接口定义历史表
#### interfacedefhistory接口定义历史表
- **功能**:存储系统接口的定义和版本历史
- **主要字段**
- `id`: VARCHAR(255) NOT NULL PRIMARY KEY - 接口定义历史记录ID
@ -201,6 +239,9 @@
- `project_id`: VARCHAR(255) - 关联的项目ID
- `url`: VARCHAR(255) NOT NULL - 接口URL
- `version`: INT - 版本号
- `created_at`: DATETIME - 创建时间
- `updated_at`: DATETIME - 更新时间
- `is_deleted`: DATETIME - 是否删除(软删除标记)
- UNIQUE INDEX `uniq`(`method`, `url`, `version`) - 确保接口定义唯一
## 3. 表关系图

Binary file not shown.

3
app.py
View File

@ -23,6 +23,7 @@ from routes.task_api import router as task_router
from routes.common_api import router as common_router, format_response
from routes.task_edit_api import router as task_edit_router
from routes.script_api import router as script_router
from routes.task_record_api import router as task_record_router
# 设置日志
logger = get_logger("app")
@ -216,7 +217,7 @@ app.include_router(template_router)
app.include_router(task_router)
app.include_router(task_edit_router)
app.include_router(script_router)
app.include_router(task_record_router)
# 根路由
@app.get("/")
async def root():

View File

@ -377,6 +377,13 @@
"id": 1,
"name": "b1",
"blockType": "CSelectAgvBp",
"inputParams": {
"keyRoute": {
"type": "Simple",
"value": "TK01",
"required": true
}
},
"children": {
"default": [
{
@ -401,13 +408,6 @@
}
]
},
"inputParams": {
"keyRoute": {
"type": "Simple",
"value": "TK01",
"required": true
}
},
"refTaskDefId": "",
"selected": false,
"expanded": true
@ -419,15 +419,93 @@
"expanded": true
}
}
=====================
==================================
{
"inputParams": [],
"outputParams": [],
"rootBlock": {
"id": -1,
"name": "-1",
"blockType": "RootBp",
"inputParams": {},
"children": {
"default": [
{
"id": 1,
"name": "b3",
"blockType": "CSelectAgvBp",
"inputParams": {
"priority": {
"type": "Simple",
"value": null,
"required": false
},
"vehicle": {
"type": "Simple",
"value": null,
"required": false
},
"group": {
"type": "Simple",
"value": null,
"required": false
},
"keyRoute": {
"type": "Simple",
"value": "TK01",
"required": true
}
},
"children": {
"default": [
{
"id": 1,
"name": "b4",
"blockType": "CAgvOperationBp",
"inputParams": {
"targetSiteLabel": {
"type": "Simple",
"value": "PT02",
"required": true
},
"spin": {
"type": "Simple",
"value": false,
"required": false
},
"task": {
"type": "Simple",
"value": "JackUnload",
"required": false
}
},
"children": {},
"refTaskDefId": "",
"selected": false,
"expanded": true
}
]
},
"refTaskDefId": "",
"selected": false,
"expanded": true
}
]
},
"refTaskDefId": "",
"selected": false,
"expanded": true
}
}
==============================
{
"inputParams": [
{
"name": "测试",
"type": "Boolean",
"label": "132",
"name": "",
"type": "String",
"label": "",
"remark": "",
"defaultValue": "213",
"defaultValue": "",
"required": false
}
],
@ -442,7 +520,7 @@
{
"id": 1,
"name": "b1",
"blockType": "IterateListBp",
"blockType": "WhileBp",
"children": {
"default": [
{
@ -453,7 +531,7 @@
"inputParams": {
"message": {
"type": "Expression",
"value": "blocks.b1.item"
"value": "========="
}
},
"refTaskDefId": "",
@ -463,10 +541,14 @@
]
},
"inputParams": {
"list": {
"loopCondition": {
"type": "Simple",
"value": "[1,2,3,4,5]",
"value": true,
"required": true
},
"runOnce": {
"type": "Simple",
"value": null
}
},
"refTaskDefId": "",

View File

@ -260,8 +260,8 @@ class BaseConfig(BaseSettings):
TASK_EXPORT_IV: str = Field(default="vwed1234task5678", env="TASK_EXPORT_IV") # 初始化向量
# 增强版任务调度器配置
TASK_SCHEDULER_MIN_WORKER_COUNT: int = 5 # 最小工作线程数
TASK_SCHEDULER_MAX_WORKER_COUNT: int = 20 # 最大工作线程数
TASK_SCHEDULER_MIN_WORKER_COUNT: int = 15 # 最小工作线程数
TASK_SCHEDULER_MAX_WORKER_COUNT: int = 30 # 最大工作线程数
TASK_SCHEDULER_QUEUE_COUNT: int = 3 # 队列数量
TASK_SCHEDULER_QUEUE_THRESHOLD_PERCENTILES: List[float] = [0.1, 0.3, 1.0] # 队列阈值百分比配置
TASK_SCHEDULER_WORKER_RATIOS: List[float] = [0.6, 0.3, 0.1] # 工作线程分配比例
@ -274,8 +274,8 @@ class BaseConfig(BaseSettings):
TASK_SCHEDULER_ZOMBIE_TASK_CHECK_INTERVAL: int = 60 # 僵尸任务检查间隔(秒)
TASK_SCHEDULER_CPU_THRESHOLD: float = 80.0 # CPU使用率阈值百分比
TASK_SCHEDULER_MEMORY_THRESHOLD: float = 80.0 # 内存使用率阈值(百分比)
TASK_SCHEDULER_AUTO_SCALE_INTERVAL: int = 60 # 自动扩缩容间隔(秒)
TASK_SCHEDULER_WORKER_HEARTBEAT_INTERVAL: int = 30 # 心跳间隔(秒)
TASK_SCHEDULER_AUTO_SCALE_INTERVAL: int = 120 # 自动扩缩容间隔(秒)
TASK_SCHEDULER_WORKER_HEARTBEAT_INTERVAL: int = 120 # 心跳间隔(秒)
@property
def DATABASE_URL(self) -> str:

View File

@ -16,10 +16,12 @@ from data.models.datacache import VWEDDataCache
from data.models.datacachesplit import VWEDDataCacheSplit
from data.models.script import VWEDScript, VWEDScriptVersion, VWEDScriptLog
from data.models.modbusconfig import VWEDModbusConfig
from data.models.calldevice import VWEDCallDevice, VWEDCallDeviceButton
# 导出所有模型供应用程序使用
__all__ = [
'BaseModel', 'VWEDTaskDef', 'VWEDTaskRecord', 'VWEDTaskLog',
'VWEDBlockRecord', 'VWEDTaskTemplate', 'VWEDDataCache', 'VWEDDataCacheSplit',
'VWEDScript', 'VWEDScriptVersion', 'VWEDScriptLog', 'VWEDModbusConfig'
'VWEDScript', 'VWEDScriptVersion', 'VWEDScriptLog', 'VWEDModbusConfig',
'VWEDCallDevice', 'VWEDCallDeviceButton'
]

Binary file not shown.

View File

@ -47,7 +47,7 @@ class VWEDBlockRecord(BaseModel):
started_on = Column(DATETIME(fsp=6), comment='开始时间')
ended_on = Column(DATETIME(fsp=6), comment='结束时间')
ended_reason = Column(LONGTEXT, comment='结束原因')
status = Column(Integer, comment='块执行状态, 1000: 执行成功, 1001: 执行中, 1002: 未执行, 2000: 执行失败, 2002: 取消')
status = Column(Integer, comment='块执行状态, 1000: 执行成功, 1001: 执行中, 1006: 未执行, 2000: 执行失败, 2002: 取消')
ctrl_status = Column(Integer, comment='控制状态')
input_params = Column(LONGTEXT, comment='输入参数')
internal_variables = Column(LONGTEXT, comment='内部变量')

89
data/models/calldevice.py Normal file
View File

@ -0,0 +1,89 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
呼叫器设备模型
对应vwed_calldevice表和vwed_calldevice_button表
"""
import datetime
import json
from sqlalchemy import Column, String, Integer, Boolean, DateTime, Text, Index, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.dialects.mysql import LONGTEXT
from data.models.base import BaseModel
class VWEDCallDevice(BaseModel):
"""
呼叫器设备模型
对应vwed_calldevice表
功能定义呼叫器设备配置
"""
__tablename__ = 'vwed_calldevice'
__table_args__ = (
Index('idx_vwed_calldevice_protocol', 'protocol'),
Index('idx_vwed_calldevice_brand', 'brand'),
Index('idx_vwed_calldevice_ip', 'ip'),
Index('idx_vwed_calldevice_device_name', 'device_name'),
Index('idx_vwed_calldevice_status', 'status'),
{
'mysql_engine': 'InnoDB',
'mysql_charset': 'utf8mb4',
'mysql_collate': 'utf8mb4_general_ci',
'info': {'order_by': 'created_at DESC'}
}
)
id = Column(String(255), primary_key=True, nullable=False, comment='呼叫器设备唯一标识')
protocol = Column(String(50), nullable=False, comment='协议类型')
brand = Column(String(100), nullable=False, comment='品牌')
ip = Column(String(50), nullable=False, comment='IP地址')
port = Column(Integer, nullable=False, comment='端口号')
device_name = Column(String(100), nullable=False, comment='设备名称')
status = Column(Integer, default=0, comment='状态(0:禁用,1:启用)')
# 定义关联关系
buttons = relationship("VWEDCallDeviceButton", back_populates="call_device", cascade="all, delete-orphan")
def __repr__(self):
return f"<VWEDCallDevice(id='{self.id}', protocol='{self.protocol}', brand='{self.brand}', ip='{self.ip}', port='{self.port}', device_name='{self.device_name}', status='{self.status}')>"
def to_dict(self):
result = super().to_dict()
result['buttons'] = [button.to_dict() for button in self.buttons]
return result
class VWEDCallDeviceButton(BaseModel):
"""
呼叫器设备按钮模型
对应vwed_calldevice_button表
功能定义呼叫器设备的按钮配置
"""
__tablename__ = 'vwed_calldevice_button'
__table_args__ = (
Index('idx_vwed_calldevice_button_device_id', 'device_id'),
Index('idx_vwed_calldevice_button_button_address', 'button_address'),
Index('idx_vwed_calldevice_button_light_address', 'light_address'),
{
'mysql_engine': 'InnoDB',
'mysql_charset': 'utf8mb4',
'mysql_collate': 'utf8mb4_general_ci',
'info': {'order_by': 'created_at DESC'}
}
)
id = Column(String(255), primary_key=True, nullable=False, comment='按钮唯一标识')
device_id = Column(String(255), ForeignKey('vwed_calldevice.id'), nullable=False, comment='所属呼叫器设备ID')
button_address = Column(String(100), nullable=False, comment='按钮地址')
remark = Column(String(255), comment='备注')
light_address = Column(String(100), comment='灯地址')
press_task_id = Column(String(255), comment='按下灯亮绑定的VWED任务ID')
long_press_task_id = Column(String(255), comment='长按取消后触发VWED任务ID')
# 定义关联关系
call_device = relationship("VWEDCallDevice", back_populates="buttons")
def __repr__(self):
return f"<VWEDCallDeviceButton(id='{self.id}', device_id='{self.device_id}', button_address='{self.button_address}', remark='{self.remark}', light_address='{self.light_address}')>"

View File

@ -37,7 +37,6 @@ class InterfaceDefHistory(BaseModel):
)
id = Column(String(255), primary_key=True, nullable=False, comment='接口定义历史记录ID')
create_date = Column(DateTime, default=datetime.datetime.now, comment='创建日期')
detail = Column(LONGTEXT, comment='接口详细定义JSON格式')
method = Column(String(255), nullable=False, comment='请求方法(GET, POST, PUT, DELETE等)')
project_id = Column(String(255), comment='关联的项目ID')

View File

@ -37,8 +37,6 @@ class VWEDModbusConfig(BaseModel):
task_id = Column(String(255), comment='任务ID')
target_value = Column(Integer, comment='目标值')
remark = Column(String(255), comment='备注')
create_date = Column(DateTime, default=datetime.datetime.now, comment='创建时间')
update_date = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='更新时间')
status = Column(Integer, default=0, comment='状态1:启用, 0:禁用)')
tenant_id = Column(String(255), comment='租户ID')

View File

@ -38,9 +38,7 @@ class VWEDScript(BaseModel):
is_public = Column(BIT(1), nullable=False, default=1, comment='是否公开')
tags = Column(String(255), comment='标签,用于分类查询')
created_by = Column(String(255), comment='创建者')
created_on = Column(DateTime, default=datetime.datetime.now, comment='创建时间')
updated_by = Column(String(255), comment='最后更新者')
updated_on = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='最后更新时间')
test_params = Column(LONGTEXT, comment='测试参数(JSON格式)')
# 关联关系
@ -71,8 +69,6 @@ class VWEDScriptVersion(BaseModel):
code = Column(LONGTEXT, nullable=False, comment='该版本代码')
change_log = Column(Text, comment='版本变更说明')
created_by = Column(String(255), comment='创建者')
created_on = Column(DateTime, default=datetime.datetime.now, comment='创建时间')
# 关联关系
script = relationship("VWEDScript", back_populates="versions")

View File

@ -36,9 +36,10 @@ class VWEDTaskLog(BaseModel):
id = Column(String(255), primary_key=True, nullable=False, comment='日志记录ID')
level = Column(Integer, comment='日志级别1: 信息, 3: 错误等)')
message = Column(LONGTEXT, comment='日志消息内容')
task_block_id = Column(Integer, comment='任务块ID')
task_block_id = Column(String(255), comment='任务块ID')
task_id = Column(String(255), comment='对应的任务定义ID')
task_record_id = Column(String(255), comment='对应的任务执行记录ID')
block_record_id = Column(String(255), comment='对应的任务块执行记录ID')
def __repr__(self):
return f"<VWEDTaskLog(id='{self.id}', level='{self.level}')>"

View File

@ -1,34 +0,0 @@
{
"9f808ed8-3551-4f19-bcd0-5a414bc36d54": {
"id": "9f808ed8-3551-4f19-bcd0-5a414bc36d54",
"priority": 1,
"info": {
"id": "9f808ed8-3551-4f19-bcd0-5a414bc36d54",
"def_id": "a0734d2a-c7a8-4ae9-99ce-c6cd82355983",
"def_label": "第一级子任务",
"def_version": 2,
"status": 1001,
"created_on": "2025-04-21 15:52:53",
"priority": 1,
"periodic_task": 0,
"input_params": {}
},
"timestamp": "2025-04-21 15:55:48"
},
"c3b19b8c-bbf5-4aeb-a5cd-da8d44c1e152": {
"id": "c3b19b8c-bbf5-4aeb-a5cd-da8d44c1e152",
"priority": 1,
"info": {
"id": "c3b19b8c-bbf5-4aeb-a5cd-da8d44c1e152",
"def_id": "a0734d2a-c7a8-4ae9-99ce-c6cd82355983",
"def_label": "第一级子任务",
"def_version": 2,
"status": 1002,
"created_on": "2025-04-21 15:55:48",
"priority": 1,
"periodic_task": 0,
"input_params": {}
},
"timestamp": "2025-04-21 15:55:48"
}
}

View File

@ -1,50 +0,0 @@
{
"9f808ed8-3551-4f19-bcd0-5a414bc36d54": {
"id": "9f808ed8-3551-4f19-bcd0-5a414bc36d54",
"priority": 1,
"info": {
"id": "9f808ed8-3551-4f19-bcd0-5a414bc36d54",
"def_id": "a0734d2a-c7a8-4ae9-99ce-c6cd82355983",
"def_label": "第一级子任务",
"def_version": 2,
"status": 1001,
"created_on": "2025-04-21 15:52:53",
"priority": 1,
"periodic_task": 0,
"input_params": {}
},
"timestamp": "2025-04-21 15:55:56"
},
"c3b19b8c-bbf5-4aeb-a5cd-da8d44c1e152": {
"id": "c3b19b8c-bbf5-4aeb-a5cd-da8d44c1e152",
"priority": 1,
"info": {
"id": "c3b19b8c-bbf5-4aeb-a5cd-da8d44c1e152",
"def_id": "a0734d2a-c7a8-4ae9-99ce-c6cd82355983",
"def_label": "第一级子任务",
"def_version": 2,
"status": 1002,
"created_on": "2025-04-21 15:55:48",
"priority": 1,
"periodic_task": 0,
"input_params": {}
},
"timestamp": "2025-04-21 15:55:56"
},
"04d68bb4-c67f-421a-bc0e-3fff6c236052": {
"id": "04d68bb4-c67f-421a-bc0e-3fff6c236052",
"priority": 1,
"info": {
"id": "04d68bb4-c67f-421a-bc0e-3fff6c236052",
"def_id": "a0734d2a-c7a8-4ae9-99ce-c6cd82355983",
"def_label": "第一级子任务",
"def_version": 2,
"status": 1002,
"created_on": "2025-04-21 15:55:57",
"priority": 1,
"periodic_task": 0,
"input_params": {}
},
"timestamp": "2025-04-21 15:55:56"
}
}

View File

@ -1,67 +0,0 @@
{
"977f4df2-567d-43bd-85b5-75b3eee08d8b": {
"id": "977f4df2-567d-43bd-85b5-75b3eee08d8b",
"priority": 1,
"info": {
"id": "977f4df2-567d-43bd-85b5-75b3eee08d8b",
"def_id": "9e0e17e3-7bad-4632-9725-1c6a469c5e2a",
"def_label": "第一级父任务",
"def_version": 4,
"status": 1001,
"created_on": "2025-04-21 15:51:39",
"priority": 1,
"periodic_task": 0,
"input_params": [
{
"name": "a",
"type": "整数",
"label": "a",
"required": false,
"defaultValue": "1",
"remark": ""
},
{
"name": "b",
"type": "整数",
"label": "b",
"required": false,
"defaultValue": "1",
"remark": ""
}
]
},
"timestamp": "2025-04-21 15:56:59"
},
"9f808ed8-3551-4f19-bcd0-5a414bc36d54": {
"id": "9f808ed8-3551-4f19-bcd0-5a414bc36d54",
"priority": 1,
"info": {
"id": "9f808ed8-3551-4f19-bcd0-5a414bc36d54",
"def_id": "a0734d2a-c7a8-4ae9-99ce-c6cd82355983",
"def_label": "第一级子任务",
"def_version": 2,
"status": 1001,
"created_on": "2025-04-21 15:52:53",
"priority": 1,
"periodic_task": 0,
"input_params": {}
},
"timestamp": "2025-04-21 15:56:59"
},
"c3b19b8c-bbf5-4aeb-a5cd-da8d44c1e152": {
"id": "c3b19b8c-bbf5-4aeb-a5cd-da8d44c1e152",
"priority": 1,
"info": {
"id": "c3b19b8c-bbf5-4aeb-a5cd-da8d44c1e152",
"def_id": "a0734d2a-c7a8-4ae9-99ce-c6cd82355983",
"def_label": "第一级子任务",
"def_version": 2,
"status": 1002,
"created_on": "2025-04-21 15:55:48",
"priority": 1,
"periodic_task": 0,
"input_params": {}
},
"timestamp": "2025-04-21 15:56:59"
}
}

View File

@ -1,67 +0,0 @@
{
"977f4df2-567d-43bd-85b5-75b3eee08d8b": {
"id": "977f4df2-567d-43bd-85b5-75b3eee08d8b",
"priority": 1,
"info": {
"id": "977f4df2-567d-43bd-85b5-75b3eee08d8b",
"def_id": "9e0e17e3-7bad-4632-9725-1c6a469c5e2a",
"def_label": "第一级父任务",
"def_version": 4,
"status": 1001,
"created_on": "2025-04-21 15:51:39",
"priority": 1,
"periodic_task": 0,
"input_params": [
{
"name": "a",
"type": "整数",
"label": "a",
"required": false,
"defaultValue": "1",
"remark": ""
},
{
"name": "b",
"type": "整数",
"label": "b",
"required": false,
"defaultValue": "1",
"remark": ""
}
]
},
"timestamp": "2025-04-21 15:58:01"
},
"9f808ed8-3551-4f19-bcd0-5a414bc36d54": {
"id": "9f808ed8-3551-4f19-bcd0-5a414bc36d54",
"priority": 1,
"info": {
"id": "9f808ed8-3551-4f19-bcd0-5a414bc36d54",
"def_id": "a0734d2a-c7a8-4ae9-99ce-c6cd82355983",
"def_label": "第一级子任务",
"def_version": 2,
"status": 1001,
"created_on": "2025-04-21 15:52:53",
"priority": 1,
"periodic_task": 0,
"input_params": {}
},
"timestamp": "2025-04-21 15:58:01"
},
"c3b19b8c-bbf5-4aeb-a5cd-da8d44c1e152": {
"id": "c3b19b8c-bbf5-4aeb-a5cd-da8d44c1e152",
"priority": 1,
"info": {
"id": "c3b19b8c-bbf5-4aeb-a5cd-da8d44c1e152",
"def_id": "a0734d2a-c7a8-4ae9-99ce-c6cd82355983",
"def_label": "第一级子任务",
"def_version": 2,
"status": 1002,
"created_on": "2025-04-21 15:55:48",
"priority": 1,
"periodic_task": 0,
"input_params": {}
},
"timestamp": "2025-04-21 15:58:01"
}
}

View File

@ -1,18 +0,0 @@
{
"c3b19b8c-bbf5-4aeb-a5cd-da8d44c1e152": {
"id": "c3b19b8c-bbf5-4aeb-a5cd-da8d44c1e152",
"priority": 1,
"info": {
"id": "c3b19b8c-bbf5-4aeb-a5cd-da8d44c1e152",
"def_id": "a0734d2a-c7a8-4ae9-99ce-c6cd82355983",
"def_label": "第一级子任务",
"def_version": 2,
"status": 1002,
"created_on": "2025-04-21 15:55:48",
"priority": 1,
"periodic_task": 0,
"input_params": {}
},
"timestamp": "2025-04-21 15:58:39"
}
}

View File

@ -0,0 +1,68 @@
{
"cd697757-c8f5-4ff6-97b4-9424eae5e835": {
"id": "cd697757-c8f5-4ff6-97b4-9424eae5e835",
"priority": 1,
"info": {
"id": "cd697757-c8f5-4ff6-97b4-9424eae5e835",
"def_id": "e22cacb4-a580-45ba-949e-356f57fa1a43",
"def_label": "第二级子任务",
"def_version": 2,
"status": 1001,
"created_on": "2025-05-06 14:25:11",
"priority": 1,
"periodic_task": 0,
"input_params": [
{
"name": "a",
"type": "INTEGER",
"label": "a",
"required": false,
"defaultValue": "1",
"remark": ""
},
{
"name": "b",
"type": "INTEGER",
"label": "b",
"required": false,
"defaultValue": "1",
"remark": ""
}
]
},
"timestamp": "2025-05-06 14:28:09"
},
"7238d241-94e8-4205-b788-e760f1f787f4": {
"id": "7238d241-94e8-4205-b788-e760f1f787f4",
"priority": 1,
"info": {
"id": "7238d241-94e8-4205-b788-e760f1f787f4",
"def_id": "e22cacb4-a580-45ba-949e-356f57fa1a43",
"def_label": "第二级子任务",
"def_version": 2,
"status": 1002,
"created_on": "2025-05-06 14:27:21",
"priority": 1,
"periodic_task": 0,
"input_params": [
{
"name": "a",
"type": "INTEGER",
"label": "a",
"required": false,
"defaultValue": "1",
"remark": ""
},
{
"name": "b",
"type": "INTEGER",
"label": "b",
"required": false,
"defaultValue": "1",
"remark": ""
}
]
},
"timestamp": "2025-05-06 14:28:09"
}
}

View File

@ -0,0 +1,35 @@
{
"ee56b2f0-2262-4405-bcbe-164854736400": {
"id": "ee56b2f0-2262-4405-bcbe-164854736400",
"priority": 1,
"info": {
"id": "ee56b2f0-2262-4405-bcbe-164854736400",
"def_id": "e22cacb4-a580-45ba-949e-356f57fa1a43",
"def_label": "第二级子任务",
"def_version": 2,
"status": 1001,
"created_on": "2025-05-06 14:36:08",
"priority": 1,
"periodic_task": 0,
"input_params": [
{
"name": "a",
"type": "INTEGER",
"label": "a",
"required": false,
"defaultValue": "1",
"remark": ""
},
{
"name": "b",
"type": "INTEGER",
"label": "b",
"required": false,
"defaultValue": "1",
"remark": ""
}
]
},
"timestamp": "2025-05-06 14:38:33"
}
}

View File

@ -0,0 +1,35 @@
{
"edbaaf50-ce57-4986-9a50-d930cb14ef8b": {
"id": "edbaaf50-ce57-4986-9a50-d930cb14ef8b",
"priority": 1,
"info": {
"id": "edbaaf50-ce57-4986-9a50-d930cb14ef8b",
"def_id": "e22cacb4-a580-45ba-949e-356f57fa1a43",
"def_label": "第二级子任务",
"def_version": 2,
"status": 1001,
"created_on": "2025-05-06 16:08:40",
"priority": 1,
"periodic_task": 0,
"input_params": [
{
"name": "a",
"type": "INTEGER",
"label": "a",
"required": false,
"defaultValue": "1",
"remark": ""
},
{
"name": "b",
"type": "INTEGER",
"label": "b",
"required": false,
"defaultValue": "1",
"remark": ""
}
]
},
"timestamp": "2025-05-06 16:15:26"
}
}

70087
logs/app.log

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

19
routes/__init__.py Normal file
View File

@ -0,0 +1,19 @@
# from fastapi import FastAPI
# # 导入所有路由文件
# from routes import (
# hello_api,
# task_edit_api,
# task_record_api, # 新增的任务记录API
# # ... 其他路由模块
# )
# def register_all_routes(app: FastAPI) -> None:
# """
# 注册所有路由到FastAPI应用
# """
# # 注册各模块的路由
# app.include_router(hello_api.router)
# app.include_router(task_edit_api.router)
# app.include_router(task_record_api.router) # 注册任务记录API路由
# # ... 注册其他路由

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -215,7 +215,3 @@ class TaskListParams(PaginationParams, SortParams):
"""任务列表查询参数模型"""
keyword: Optional[str] = Field(None, description="关键字搜索(任务名称)")
status: Optional[int] = Field(None, description="任务状态筛选")
class TaskListResponse(PageResult[TaskResponse]):
"""任务列表响应模型"""
pass

View File

@ -7,8 +7,8 @@
"""
import json
from typing import Optional, List, Dict, Any
from fastapi import APIRouter, Depends, File, UploadFile, Form, Response
from typing import Optional, Dict, Any
from fastapi import APIRouter, Depends, File, UploadFile, Form, Response, Path
from sqlalchemy.orm import Session
from pydantic import ValidationError
import logging
@ -20,7 +20,7 @@ from data.session import get_db
from services.task_service import TaskService, TaskNameExistsError
from routes.model.base import ApiResponse
from routes.model.task_model import (
CreateTaskRequest, DeleteTaskRequest, ExportBatchRequest, TaskResponse, TaskListResponse, TaskListParams
CreateTaskRequest, DeleteTaskRequest, ExportBatchRequest, TaskResponse, TaskListParams
)
from routes.common_api import format_response, error_response
from utils.crypto_utils import CryptoUtils
@ -50,7 +50,7 @@ def api_response(code: int = 200, message: str = "操作成功", data: Any = Non
"data": data
}
@router.get("/list", response_model=ApiResponse[TaskListResponse])
@router.get("/list", response_model=ApiResponse)
async def get_task_list(
params: TaskListParams = Depends(),
db: Session = Depends(get_db)
@ -338,3 +338,59 @@ async def api_get_task(task_id: str, db: Session = Depends(get_db)):
except Exception as e:
logger.error(f"获取任务详情失败: {str(e)}")
return error_response(f"获取任务详情失败: {str(e)}", 500)
@router.post("/stop-all/{task_def_id}")
async def stop_task_definition(task_def_id: str = Path(..., description="任务定义ID")):
"""
停止指定任务定义下的所有运行任务实例同时禁用定时任务
Args:
task_def_id: 任务定义ID不是任务记录ID
Returns:
包含停止结果的响应包括成功停止的任务数量是否为定时任务等信息
"""
try:
# 调用服务层方法
result = await TaskService.stop_task_def(task_def_id)
if not result.get("success", False):
return error_response(
message=result.get("message", "停止任务失败"),
code=400
)
# 构造返回消息
is_periodic = result.get("is_periodic", False)
total_running = result.get("total_running", 0)
stopped_count = result.get("stopped_count", 0)
failed_count = result.get("failed_count", 0)
# 如果有停止失败的任务,记录警告日志
if failed_count > 0:
failed_tasks = result.get("failed_tasks", [])
logger.warning(f"任务定义 {task_def_id} 停止时有 {failed_count} 个任务停止失败: {failed_tasks}")
message = ""
if is_periodic:
message += "已禁用定时任务并"
if total_running > 0:
message += f"停止了 {stopped_count}/{total_running} 个运行中的任务实例"
else:
message += "没有运行中的任务实例需要停止"
return format_response(
data={
"taskDefId": task_def_id,
"isPeriodic": is_periodic,
"totalRunning": total_running,
"stoppedCount": stopped_count,
"failedCount": result.get("failed_count", 0),
"failedTasks": result.get("failed_tasks", [])
},
message=message
)
except Exception as e:
logger.log_error_with_trace(f"停止任务定义异常", e)
return error_response(message=f"停止任务失败: {str(e)}", code=500)

View File

@ -326,60 +326,6 @@ async def stop_task(task_record_id: str = Path(..., description="任务记录ID"
logger.log_error_with_trace(f"终止任务异常", e)
return error_response(message=f"终止任务失败: {str(e)}", code=500)
@router.post("/stop-all/{task_def_id}")
async def stop_task_definition(task_def_id: str = Path(..., description="任务定义ID")):
"""
停止指定任务定义下的所有运行任务实例同时禁用定时任务
Args:
task_def_id: 任务定义ID不是任务记录ID
Returns:
包含停止结果的响应包括成功停止的任务数量是否为定时任务等信息
"""
try:
# 调用服务层方法
result = await TaskEditService.stop_task_def(task_def_id)
if not result.get("success", False):
return error_response(
message=result.get("message", "停止任务失败"),
code=400
)
# 构造返回消息
is_periodic = result.get("is_periodic", False)
total_running = result.get("total_running", 0)
stopped_count = result.get("stopped_count", 0)
failed_count = result.get("failed_count", 0)
# 如果有停止失败的任务,记录警告日志
if failed_count > 0:
failed_tasks = result.get("failed_tasks", [])
logger.warning(f"任务定义 {task_def_id} 停止时有 {failed_count} 个任务停止失败: {failed_tasks}")
message = ""
if is_periodic:
message += "已禁用定时任务并"
if total_running > 0:
message += f"停止了 {stopped_count}/{total_running} 个运行中的任务实例"
else:
message += "没有运行中的任务实例需要停止"
return format_response(
data={
"taskDefId": task_def_id,
"isPeriodic": is_periodic,
"totalRunning": total_running,
"stoppedCount": stopped_count,
"failedCount": result.get("failed_count", 0),
"failedTasks": result.get("failed_tasks", [])
},
message=message
)
except Exception as e:
logger.log_error_with_trace(f"停止任务定义异常", e)
return error_response(message=f"停止任务失败: {str(e)}", code=500)
@router.post("/input-params/{id}")
async def save_input_params(
@ -425,7 +371,7 @@ async def get_input_param_types(
"""
try:
result = await TaskEditService.get_task_input_params(id)
return format_response(data=result)
return result
except Exception as e:
return error_response(message=f"获取任务输入参数配置失败: {str(e)}", code=500)

137
routes/task_record_api.py Normal file
View File

@ -0,0 +1,137 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
任务运行记录API模块
提供任务运行记录相关的API接口
"""
from typing import Dict, List, Any, Optional
from fastapi import APIRouter, Query, Path, Request
from routes.common_api import format_response, error_response
from utils.logger import get_logger
from services.task_record_service import TaskRecordService
# 创建路由
router = APIRouter(
prefix="/api/vwed-task-record",
tags=["VWED任务运行记录"]
)
# 设置日志
logger = get_logger("app.task_record_api")
@router.get("/blocks/{task_record_id}")
async def get_task_blocks(
task_record_id: str = Path(..., description="任务记录ID")
):
"""
获取指定任务记录下的所有块运行情况
Args:
task_record_id: 任务记录ID由run_task接口返回的taskRecordId
Returns:
包含该任务记录下所有块运行记录的响应
"""
try:
result = await TaskRecordService.get_task_blocks(task_record_id)
if not result["success"]:
return error_response(message=result["message"], code=500)
return format_response(data=result["data"], message=result["message"])
except Exception as e:
logger.error(f"获取任务块运行情况失败: {str(e)}")
return error_response(message=f"获取任务块运行情况失败: {str(e)}", code=500)
@router.get("/block/{block_record_id}")
async def get_block_detail(
block_record_id: str = Path(..., description="块记录ID")
):
"""
获取指定块记录的详细信息
Args:
block_record_id: 块记录ID
Returns:
包含该块记录详细信息的响应
"""
try:
result = await TaskRecordService.get_block_detail(block_record_id)
if not result["success"]:
return error_response(message=result["message"], code=404)
return format_response(data=result["data"], message=result["message"])
except Exception as e:
logger.error(f"获取块记录详情失败: {str(e)}")
return error_response(message=f"获取块记录详情失败: {str(e)}", code=500)
@router.post("/stop/{task_record_id}")
async def stop_task_definition(task_record_id: str = Path(..., description="任务记录ID")):
"""
停止指定任务记录下的所有运行任务实例同时禁用定时任务
Args:
task_record_id: 任务记录ID
Returns:
包含停止结果的响应包括成功停止的任务数量是否为定时任务等信息
"""
try:
# 调用服务层方法
result = await TaskRecordService.stop_task_record(task_record_id)
if not result.get("success", False):
return error_response(
message=result.get("message", "停止任务失败"),
code=400
)
return format_response(
data=result.get("data", {}),
message=result.get("message", "任务终止成功")
)
except Exception as e:
logger.log_error_with_trace(f"停止任务定义异常", e)
return error_response(message=f"任务记录终止失败: {str(e)}", code=500)
@router.get("/execution/block/results/{task_record_id}")
async def get_block_result(task_record_id: str = Path(..., description="任务记录ID")):
"""
获取指定任务记录的执行结果
Args:
task_record_id: 任务记录ID
Returns:
包含执行结果的响应
"""
try:
result = await TaskRecordService.get_block_results(task_record_id)
if not result["success"]:
return error_response(message=result["message"], code=404)
return format_response(data=result["data"], message=result["message"])
except Exception as e:
logger.error(f"获取任务记录执行结果失败: {str(e)}")
return error_response(message=f"获取任务记录执行结果失败: {str(e)}", code=500)
@router.get("/detail/{task_record_id}")
async def get_task_record_detail(
task_record_id: str = Path(..., description="任务记录ID")
):
"""
获取指定任务记录的详细信息
Args:
task_record_id: 任务记录ID
Returns:
包含该任务记录详细信息的响应
"""
try:
result = await TaskRecordService.get_task_record_detail(task_record_id)
if not result.get("success", False):
return error_response(message=result.get("message", "任务记录不存在"), code=404)
return format_response(data=result.get("data", {}), message="成功获取任务记录详情")
except Exception as e:
logger.error(f"获取任务记录详情失败: {str(e)}")
return error_response(message=f"获取任务记录详情失败: {str(e)}", code=500)

View File

@ -264,11 +264,7 @@ class PeriodicTaskManager:
now = base_time or datetime.now()
# 使用简单的周期计算
total_delay_seconds = (period_ms + delay_ms) / 1000 # 转换为秒
if base_time is None:
# 首次计算时考虑延迟
total_delay_seconds = delay_ms / 1000
total_delay_seconds = (period_ms + delay_ms) / 1000 # 转换为秒
return now + timedelta(seconds=total_delay_seconds)
def get_task_status(self) -> Dict[str, Any]:

View File

@ -91,8 +91,7 @@ class PriorityQueueManager:
# 添加到队列(使用负优先级值)
await self.priority_queues[queue_index].put((-priority, task_id))
logger.debug(f"任务 {task_id} (优先级 {priority}) 添加到队列 {queue_index}")
logger.info(f"任务 {task_id} (优先级 {priority}) 添加到队列 {queue_index}")
return queue_index
@ -109,7 +108,6 @@ class PriorityQueueManager:
"""
# 确定工作线程应该从哪个队列获取任务
queue_index = self._get_worker_queue(worker_id, worker_count)
# 检查指定队列是否有任务
if not self.priority_queues[queue_index].empty():
item = await self.priority_queues[queue_index].get()

View File

@ -127,7 +127,8 @@ class EnhancedTaskScheduler:
cpu_threshold=self.cpu_threshold,
memory_threshold=self.memory_threshold,
auto_scale_interval=self.auto_scale_interval,
worker_heartbeat_interval=self.worker_heartbeat_interval
worker_heartbeat_interval=self.worker_heartbeat_interval,
queue_manager=self.queue_manager
)
# 设置工作线程工厂函数
@ -260,7 +261,6 @@ class EnhancedTaskScheduler:
# 检查任务状态
current_status = task_record.status
# 只恢复这些状态的任务:
# 1001:执行中(可能是上次异常退出)
# 1002:排队中
@ -365,15 +365,22 @@ class EnhancedTaskScheduler:
# 添加到队列
queue_index = await self.queue_manager.enqueue(task_record_id, priority)
return {
response = {
"success": True,
"message": "任务已提交到队列",
"taskRecordId": task_record_id,
"queueIndex": queue_index,
"queueSize": self._get_queue_size(),
"priority": priority
}
}
# 如果所有工作线程都在工作中,添加警告信息
use_workers = [worker_id for worker_id, status in self.worker_manager.worker_status.items() if status.get("current_task") is not None]
if len(use_workers) == len(self.worker_manager.worker_status):
response["warning"] = f"警告:所有工作线程({len(self.worker_manager.worker_status)}个)都在忙碌中,任务可能需要等待较长时间"
logger.warning(f"提交任务 {task_record_id} 时所有工作线程都在忙碌中,当前工作线程数: {len(self.worker_manager.worker_status)}")
return response
async def run_task(self, task_def_id: str, params: List[Dict[str, Any]] = None, parent_task_id: str = None,
root_task_id: str = None, source_type: int = None, source_system: str = None,
source_device: str = None, source_time: datetime = None, source_ip: str = None,
@ -411,7 +418,8 @@ class EnhancedTaskScheduler:
"message": "任务定义不存在",
"taskDefId": task_def_id
}
map_id = task_def.map_id
user_token = task_def.user_token
# 创建任务记录
task_record_id = str(uuid.uuid4())
task_record = VWEDTaskRecord(
@ -454,7 +462,7 @@ class EnhancedTaskScheduler:
is_periodic=task_record.periodic_task,
priority=task_record.priority,
parent_id=task_record.parent_task_record_id if task_record.parent_task_record_id else "",
token=tf_api_token,
token=user_token,
map_id=map_id,
is_agv=select_agv_type
)

View File

@ -30,7 +30,8 @@ class WorkerManager:
worker_heartbeat_interval: int = 30,
auto_scale_interval: int = 60,
cpu_threshold: float = 80.0,
memory_threshold: float = 80.0):
memory_threshold: float = 80.0,
queue_manager = None):
"""
初始化工作线程管理器
@ -58,7 +59,8 @@ class WorkerManager:
self.monitor_task = None # 监控任务
self.last_auto_scale_time = datetime.now()
self.queue_manager = queue_manager
logger.info(f"初始化工作线程管理器: min={min_workers}, max={max_workers}, "
f"心跳间隔={worker_heartbeat_interval}秒, 自动扩缩容间隔={auto_scale_interval}")
@ -95,7 +97,7 @@ class WorkerManager:
await self.add_worker()
# 启动监控任务
# self.monitor_task = asyncio.create_task(self._monitor_workers())
self.monitor_task = asyncio.create_task(self._monitor_workers())
logger.info(f"工作线程管理器启动成功,初始工作线程数: {initial_count}")
@ -221,24 +223,21 @@ class WorkerManager:
try:
# 检查工作线程心跳
now = datetime.now()
print("self.worker_heartbeats",self.worker_heartbeats)
print("self.worker_status",self.worker_status)
for worker_id, last_heartbeat in list(self.worker_heartbeats.items()):
if (now - last_heartbeat).total_seconds() > self.worker_heartbeat_interval * 2:
logger.warning(f"工作线程 {worker_id} 心跳超时,重启中...")
# 重启工作线程
await self.remove_worker(worker_id)
await self.add_worker()
if self.worker_status[worker_id].get("current_task",None) is None:
logger.info(f"工作线程 {worker_id} 心跳超时,重启中...")
# 重启工作线程
await self.remove_worker(worker_id)
await self.add_worker()
else:
logger.info(f"工作线程 {worker_id} 心跳超时,但当前有任务,不重启")
# 自动扩缩容
if (now - self.last_auto_scale_time).total_seconds() > self.auto_scale_interval:
await self._auto_scale()
self.last_auto_scale_time = now
# 休眠一段时间
await asyncio.sleep(self.worker_heartbeat_interval / 2)
await asyncio.sleep(self.worker_heartbeat_interval)
except asyncio.CancelledError:
# 取消异常,退出循环
logger.info("工作线程监控任务被取消")
@ -249,40 +248,106 @@ class WorkerManager:
await asyncio.sleep(5.0)
logger.info("工作线程监控任务结束")
async def _check_workers(self, flag: bool = False) -> List[int]:
"""
检查指定的工作线程
"""
worker_ids = []
unused_worker_ids = []
for worker_id, _ in list(self.worker_heartbeats.items()):
if self.worker_status[worker_id].get("current_task",None) is None:
unused_worker_ids.append(worker_id)
else:
worker_ids.append(worker_id)
if flag:
return unused_worker_ids
else:
return worker_ids
async def _delete_unused_workers(self) -> bool:
"""
删除空闲工作线程
"""
import random
worker_ids = await self._check_workers(flag=True)
if len(worker_ids) > 0:
worker_id = random.choice(worker_ids)
await self.remove_worker(worker_id)
logger.info(f"移除未使用的空闲工作线程 {worker_id}")
return True
else:
logger.info("没有未使用的空闲工作线程")
return False
def _calculate_threads_to_add(self, queue_size: int, current_workers: int) -> int:
"""
根据队列大小和当前工作线程数计算需要增加的线程数
Args:
queue_size: 队列中的任务数量
current_workers: 当前正在运行任务的线程数
Returns:
int: 需要增加的线程数
"""
total_tasks = queue_size + current_workers
current_pool_size = len(self.workers)
remaining_capacity = self.max_workers - current_pool_size
if remaining_capacity <= 0:
return 0
# 计算任务与线程的比率
task_thread_ratio = total_tasks / max(1, current_pool_size)
# 根据任务与线程比率动态计算增加的线程数
if task_thread_ratio > 3.0:
# 负载非常高,尝试增加更多线程
threads_to_add = min(remaining_capacity, max(3, total_tasks // 3))
elif task_thread_ratio > 2.0:
# 中等负载,适量增加线程
threads_to_add = min(remaining_capacity, max(2, total_tasks // 4))
else:
# 轻微负载,小幅增加线程
threads_to_add = min(remaining_capacity, 1)
return threads_to_add
async def _auto_scale(self) -> None:
"""
自动扩缩容
根据系统资源使用情况和任务队列长度自动调整工作线程数量
"""
try:
current_workers = len(self.workers)
# 获取系统资源使用情况
current_workers = len(await self._check_workers(flag=False))
cpu_percent = psutil.cpu_percent()
memory_percent = psutil.virtual_memory().percent
# 获取系统资源使用情况
memory_percent = psutil.virtual_memory().percent # 获取系统的虚拟内存(包括物理内存和交换内存)的使用率
# 获取任务队列长度(需要外部传入或通过其他方式获取)
queue_size = self.get_queue_size()
# 根据资源使用情况和队列长度决定是否调整工作线程数量
if cpu_percent > self.cpu_threshold or memory_percent > self.memory_threshold:
# 资源使用率过高,减少工作线程
if current_workers > self.min_workers:
# 移除最后添加的工作线程
worker_id = max(self.workers.keys())
await self.remove_worker(worker_id)
logger.info(f"资源使用率过高(CPU:{cpu_percent}%, MEM:{memory_percent}%), 减少工作线程至 {current_workers-1}")
elif queue_size > current_workers * 2:
# 队列任务较多,增加工作线程
if current_workers < self.max_workers:
await self.add_worker()
logger.info(f"队列任务较多(size:{queue_size}), 增加工作线程至 {current_workers+1}")
elif queue_size == 0 and current_workers > self.min_workers:
# 移除最后添加的工作线程
if len(self.workers) > self.min_workers:
if await self._delete_unused_workers():
logger.info(f"资源使用率过高(CPU:{cpu_percent}%, MEM:{memory_percent}%), 减少工作线程至 {current_workers-1}")
elif queue_size+current_workers > len(self.workers):
# 队列任务较多,动态增加工作线程
threads_to_add = self._calculate_threads_to_add(queue_size, current_workers)
if threads_to_add > 0:
for _ in range(threads_to_add):
await self.add_worker()
logger.info(f"队列任务较多(size:{queue_size}, running:{current_workers}), "
f"动态增加{threads_to_add}个工作线程至 {len(self.workers)}")
elif queue_size == 0 and len(self.workers) > self.min_workers:
# 队列为空,减少工作线程
worker_id = max(self.workers.keys())
await self.remove_worker(worker_id)
logger.info(f"队列为空,减少工作线程至 {current_workers-1}")
if await self._delete_unused_workers():
logger.info(f"队列为空,减少工作线程至 {current_workers-1}")
except Exception as e:
logger.error(f"自动扩缩容异常: {str(e)}")
@ -296,7 +361,7 @@ class WorkerManager:
int: 队列长度
"""
# 此方法应由子类重写或设置外部方法
return 0
return sum(self.queue_manager.get_queue_sizes())
def set_queue_size_getter(self, getter: Callable[[], int]) -> None:
"""

View File

@ -67,7 +67,11 @@ class BlockExecutor:
"message": "任务已被取消",
"block_id": block.get("id", "unknown")
}
if block is None:
return {
"success": False,
"message": "核心执行逻辑中的逻辑块不能为空!"
}
block_id = block.get("id", "unknown")
block_type = block.get("blockType", "unknown")
block_name = block.get("name", f"block-{block_id}")
@ -82,8 +86,7 @@ class BlockExecutor:
block_record_id = await self._create_block_record(block)
# 记录执行路径
self.task_context.add_execution_path(block_id)
self.task_context.add_execution_path(block_id)
# 解析输入参数
input_params = await self._parse_input_params(block.get("inputParams", {}))
@ -190,7 +193,6 @@ class BlockExecutor:
# 这主要用于条件块等流程控制,当条件不满足时避免执行子块
if parent_output and isinstance(parent_output, dict) and parent_output.get("executed") is False:
logger.info(f"{block_id} 的输出包含executed=False标志跳过执行子块")
# 虽然不执行子块,但仍然为每个子块创建记录并标记为"未执行"状态
results = []
for i, child_block in enumerate(children):
@ -249,7 +251,6 @@ class BlockExecutor:
start_execution = True
# 清除跳转标记,避免影响后续执行
self.task_context.clear_skip_to()
# 如果当前还未开始执行(处于跳过状态),为块创建"未执行"记录
if not start_execution:
logger.info(f"跳过执行子块 [{i+1}/{len(children)}] - 名称: {child_name}, ID: {child_id}, 类型: {child_type}")
@ -576,7 +577,14 @@ class BlockExecutor:
match = re.search(r'\[(.*)\]', input_str)
if match:
# 获取括号内的内容并按逗号分割
items = [item.strip() for item in match.group(1).split(',')]
items = []
for item in match.group(1).split(','):
try:
json.loads(item)
json_item = eval(item)
except Exception as e:
json_item = item.strip()
items.append(json_item)
return items
return input_str
@ -589,7 +597,6 @@ class BlockExecutor:
# 处理不同类型的参数
param_type = value_info.get("type", TaskInputParamType.SIMPLE)
param_value = value_info.get("value")
if param_type == TaskInputParamType.SIMPLE:
# 简单值,直接使用
parsed_params[key] = param_value
@ -651,9 +658,8 @@ class BlockExecutor:
"""
if not isinstance(expression_value, str):
return expression_value
# 检查是否包含加号,表示需要合并多个对象
if "+" in expression_value and not (expression_value.startswith("") and "." in expression_value):
if "+" in expression_value and "." in expression_value:
return await self._parse_combined_expression(expression_value)
# 检查是否是数据库表引用的表达式 (如: vwed_taskdef.id)
elif expression_value.startswith(TaskInputParamVariables.VWED_) and "." in expression_value:
@ -669,8 +675,54 @@ class BlockExecutor:
return await self._parse_string_utils_expression(expression_value)
else:
# 非数据库表引用,尝试从上下文变量中获取
return self.task_context.get_variable(expression_value)
return expression_value
def _split_expression_by_plus(self, expression: str) -> List[str]:
"""
按照加号分割表达式但忽略引号内的加号
Args:
expression: 需要分割的表达式
Returns:
List[str]: 分割后的表达式部分列表
"""
parts = []
current_part = ""
in_quotes = False
quote_char = None
num_error = 0
num_left_quote = 0
for index, char in enumerate(expression):
# 处理引号
if char in ['(', ")"]:
num_error += 1
if num_error == 1 and char in ['', ""]:
raise Exception(f"表达式 {expression} 格式不规范")
if char in ['"', "'"]:
if not in_quotes: # 开始引号
in_quotes = True
quote_char = char
elif char == quote_char: # 结束引号
in_quotes = False
quote_char = None
num_left_quote += 1
current_part += char
if num_left_quote == 1 and char == "":
raise Exception(f"表达式 {expression} 格式不规范")
elif char == '+' and not in_quotes: # 只处理引号外的加号作为分隔符
if current_part.strip():
parts.append(current_part.strip())
current_part = ""
else:
current_part += char
# 添加最后一部分
if current_part.strip():
parts.append(current_part.strip())
return parts
async def _parse_combined_expression(self, expression: str) -> Any:
"""
解析组合表达式 "a + b + c"
@ -681,10 +733,10 @@ class BlockExecutor:
Returns:
Any: 解析结果
"""
# 分割表达式,获取各部分
parts = [p.strip() for p in expression.split("+")]
# 分割表达式,获取各部分,但需要处理引号内的加号
parts = self._split_expression_by_plus(expression)
result_values = []
# 处理每个部分
for part in parts:
part_value = None
@ -692,6 +744,7 @@ class BlockExecutor:
# 检查是否是数据库表引用
if part.startswith(TaskInputParamVariables.VWED_) and "." in part:
part_value = await self._parse_db_reference(part)
# 检查是否是块输出引用b_reference(part)
# 检查是否是块输出引用
elif part.startswith(TaskInputParamVariables.BLOCKS):
part_value = self._parse_block_reference(part)
@ -708,7 +761,6 @@ class BlockExecutor:
# 添加到结果列表
if part_value is not None:
result_values.append(part_value)
# 合并结果
if result_values:
# 检查结果类型并合并
@ -737,16 +789,14 @@ class BlockExecutor:
Returns:
Any: 引用的值
"""
_, block_name, field_name = reference.split(".")
block_output = self.task_context.get_block_output(block_name)
if block_output is None:
raise Exception(f"{reference} 获取块引用失败 表达式: {reference} 有误")
try:
_, block_name, field_name = reference.split(".")
block_output = self.task_context.get_block_output(block_name)
if isinstance(block_output, dict) and field_name in block_output:
return block_output[field_name]
else:
return block_output
return block_output[field_name]
except Exception as e:
logger.error(f"获取块引用 {reference} 失败: {str(e)}")
return None
raise Exception(f"{field_name} 变量不存在!")
async def _parse_db_reference(self, reference: str) -> Any:
"""
@ -778,7 +828,7 @@ class BlockExecutor:
except Exception as e:
# 查询失败时记录错误并返回None
logger.error(f"查询表达式 {reference} 失败: {str(e)}")
return None
raise Exception(f"{field_name} 查询相关字段不存在")
async def _query_task_record(self, session: AsyncSession, field_name: str, task_record_id: str) -> Any:
"""
@ -819,7 +869,6 @@ class BlockExecutor:
# 构建完整SQL
query = f"SELECT {actual_field} FROM {VWEDTaskRecord.__tablename__} WHERE {' AND '.join(conditions)}"
# 执行查询
result = await session.execute(text(query), query_params)
@ -827,7 +876,7 @@ class BlockExecutor:
row = result.first()
if row and len(row) > 0:
# 获取字段值
db_value = row[0]
db_value = str(row[0])
logger.info(f"从数据库 {VWEDTaskRecord.__tablename__}.{actual_field} 获取值: {db_value}")
return db_value
else:
@ -874,7 +923,6 @@ class BlockExecutor:
# 构建完整SQL
query = f"SELECT {actual_field} FROM {VWEDTaskDef.__tablename__} WHERE {' AND '.join(conditions)}"
# 执行查询
result = await session.execute(text(query), params)
row = result.first()
@ -971,10 +1019,10 @@ class BlockExecutor:
# 改为同步处理方法,避免返回协程对象
return parsed_value
except json.JSONDecodeError:
print(f"解析JSON失败: {json_value}======================================")
print(f"解析JSON失败: {json_value}")
return json_value
else:
# 已经是Python对象处理嵌套结构
# 已经是Python对象处理嵌套结构xx
return json_value
@ -1033,38 +1081,38 @@ class BlockExecutor:
# 其他基本类型直接返回
return data
def _get_possible_output_keys(self, block_name: str) -> List[str]:
"""
根据块名称获取可能的输出键
Args:
block_name: 块名称
Returns:
List[str]: 可能的输出键列表
"""
# 块类型与可能的输出变量名映射
output_keys_map = {
"CreateUuidBp": ["createUuid", "uuid"],
"CurrentTimeStampBp": ["currentTimeStamp"],
"TimestampBp": ["timestamp"],
"StringToJsonObjectBp": ["jsonObject"],
"StringToJsonArrayBp": ["jsonArray"],
"JdbcQueryBp": ["jdbcQueryResult"],
"HttpGetBp": ["httpResponse", "response"],
"HttpPostBp": ["httpResponse", "response"],
"ScriptBp": ["scriptResult"],
# 添加更多块类型和对应的输出变量名
}
# 从块名中提取类型
for block_type, keys in output_keys_map.items():
if block_type in block_name:
return keys
# 如果没有找到匹配的块类型,返回空列表
return []
# def _get_possible_output_keys(self, block_name: str) -> List[str]:
# """
# 根据块名称获取可能的输出键
#
# Args:
# block_name: 块名称
#
# Returns:
# List[str]: 可能的输出键列表
# """
# # 块类型与可能的输出变量名映射
# output_keys_map = {
# "CreateUuidBp": ["createUuid", "uuid"],
# "CurrentTimeStampBp": ["currentTimeStamp"],
# "TimestampBp": ["timestamp"],
# "StringToJsonObjectBp": ["jsonObject"],
# "StringToJsonArrayBp": ["jsonArray"],
# "JdbcQueryBp": ["jdbcQueryResult"],
# "HttpGetBp": ["httpResponse", "response"],
# "HttpPostBp": ["httpResponse", "response"],
# "ScriptBp": ["scriptResult"],
# # 添加更多块类型和对应的输出变量名
# }
#
# # 从块名中提取类型
# for block_type, keys in output_keys_map.items():
# if block_type in block_name:
# return keys
#
# # 如果没有找到匹配的块类型,返回空列表
# return []
#
async def _parse_task_inputs_reference(self, reference: str) -> Any:
"""
解析任务输入参数引用表达式 taskInputs.paramName
@ -1080,7 +1128,7 @@ class BlockExecutor:
parts = reference.split(".")
if len(parts) != 2:
logger.error(f"任务输入参数引用格式不正确: {reference},应为 'taskInputs.paramName'")
return None
raise Exception(f"{reference} 任务输入参数引用格式不正确")
param_name = parts[1]
@ -1088,7 +1136,7 @@ class BlockExecutor:
task_record_id = self.task_context.task_record_id
if not task_record_id:
logger.error("无法获取任务记录ID")
return None
raise Exception(f"{reference} 无法获取任务记录ID")
async with get_async_session() as session:
# 查询当前任务的blockrecord
@ -1104,16 +1152,15 @@ class BlockExecutor:
input_params_json = task_record.input_params
if not input_params_json:
logger.error(f"任务记录 {task_record_id} 的块记录没有输入参数")
return None
raise Exception(f"{reference} 无法获取任务记录ID")
try:
# 解析JSON字符串为Python对象
input_params = json.loads(input_params_json)
# 检查输入参数的格式
if not isinstance(input_params, list):
logger.error(f"任务输入参数格式不正确,应为列表: {input_params}")
return None
raise Exception(f"{reference} 任务输入参数格式不正确,应为列表")
# 查找参数名匹配的参数项
for param in input_params:
if isinstance(param, dict) and param.get("name") == param_name:
@ -1153,7 +1200,7 @@ class BlockExecutor:
except Exception as e:
logger.error(f"获取任务输入参数 {reference} 失败: {str(e)}")
return None
raise Exception(f"{reference} 获取任务输入参数失败")
async def _parse_string_utils_expression(self, expression_value: str) -> Any:
"""
@ -1176,7 +1223,6 @@ class BlockExecutor:
# 验证表达式格式
if not expression_value.startswith(TaskInputParamVariables.STRING_UTILS + "."):
raise ValueError(f"无效的StringUtils表达式: {expression_value}")
# 提取函数名和参数部分
function_pattern = rf"{TaskInputParamVariables.STRING_UTILS}\.(\w+)\((.*)\)$"
match = re.match(function_pattern, expression_value)
@ -1188,10 +1234,10 @@ class BlockExecutor:
# 构建函数ID (匹配BUILT_IN_FUNCTIONS中的id格式)
function_id = f"{function_name}()"
print(f"params_str: {params_str}=======================================")
# 解析参数列表
params = self._parse_params_string(params_str)
print(f"params: {params}=======================================")
# 处理每个参数,解析可能的嵌套表达式
processed_params = []
for param in params:
@ -1201,12 +1247,13 @@ class BlockExecutor:
else:
# 递归解析可能的嵌套表达式
processed_params.append(await self._parse_expression("", param))
print(f"processed_params: {processed_params}=======================================")
# 调用内置函数并返回结果
return await call_function(function_id, *processed_params)
except Exception as e:
logger.error(f"执行StringUtils表达式失败: {expression_value}, 错误: {str(e)}")
raise
raise Exception(f"{expression_value} 执行StringUtils表达式失败 错误: {str(e)}")
def _parse_params_string(self, params_str: str) -> List[str]:
"""
@ -1221,35 +1268,35 @@ class BlockExecutor:
if not params_str.strip():
return []
params = []
current_param = ""
bracket_depth = 0
quote_char = None
for char in params_str:
if char == ',' and bracket_depth == 0 and quote_char is None:
# 遇到顶层逗号,完成当前参数
params.append(current_param.strip())
current_param = ""
else:
# 处理引号
if char in ['"', "'"]:
if quote_char is None:
quote_char = char
elif char == quote_char:
quote_char = None
# params = []
params = params_str.split(",")
# current_param = ""
# bracket_depth = 0
# quote_char = None
# for index, char in enumerate(params_str):
# if char == ',' and bracket_depth == 0 and quote_char is None:
# # 遇到顶层逗号,完成当前参数
# params.append(current_param.strip())
# current_param = ""
# if index == 0 and char in ['“', "”"]:
# raise Exception(f"表达式 {params_str} 格式不规范")
# elif char in ['"', "'"]:
# if quote_char is None:
# quote_char = char
# elif char == quote_char:
# quote_char = None
# 处理括号
if quote_char is None: # 只在非引号内计算括号深度
if char == '(':
bracket_depth += 1
elif char == ')':
bracket_depth -= 1
current_param += char
# # 处理括号
# if quote_char is None: # 只在非引号内计算括号深度
# if char == '(':
# bracket_depth += 1
# elif char == ')':
# bracket_depth -= 1
# current_param += char
# 添加最后一个参数
if current_param.strip():
params.append(current_param.strip())
# # 添加最后一个参数
# if current_param.strip():
# params.append(current_param.strip())
return params

View File

@ -62,7 +62,6 @@ class BlockHandler(ABC):
from data.session import get_async_session
import uuid
from datetime import datetime
try:
# 创建任务日志记录
task_log_id = str(uuid.uuid4())
@ -80,7 +79,7 @@ class BlockHandler(ABC):
id=task_log_id,
level=1 if result.get("success", False) else 3, # 1: 信息, 3: 错误
message=json.dumps(result, ensure_ascii=False, default=json_serializer),
task_block_id=block.get("id", "unknown"),
task_block_id=block.get("name", "unknown"),
task_id=context.task_def_id,
task_record_id=context.task_record_id
)

View File

@ -564,19 +564,13 @@ class StringToJsonArrayBlockHandler(BlockHandler):
# 记录执行结果
await self._record_task_log(block, result, context)
return result
# 处理可能被额外引号包裹的情况
try:
# 首先尝试直接解析
json_array = json.loads(convert_string)
except json.JSONDecodeError:
except Exception as e:
# 如果失败,尝试去除可能的外层引号
if convert_string.startswith("'") and convert_string.endswith("'"):
convert_string = convert_string[1:-1]
elif convert_string.startswith('"') and convert_string.endswith('"'):
convert_string = convert_string[1:-1]
# 再次尝试解析
json_array = json.loads(convert_string)
json_array = convert_string
# 设置上下文输出
context.set_block_output(block.get("name"), {"convertArray": json_array})

View File

@ -347,7 +347,7 @@ class ModbusCommonReadNameBlockHandler(ModbusBlockHandler):
result = {
"success": True,
"data": {
"output": {
"modbusValue": str(modbus_value)
},
"message": f"成功读取Modbus值: 实例: {instance_name}{address_info}, 值: {modbus_value}{remark_info}"

View File

@ -205,10 +205,18 @@ class WhileBlockHandler(BlockHandler):
try:
# 获取循环条件和相关配置
loop_condition = input_params.get("loopCondition", False)
run_once = input_params.get("runOnce", False) # 是否至少执行一次
retry_period = input_params.get("retryPeriod", 0) # 循环间隔(毫秒)
print_continuously = input_params.get("printContinuously", False) # 是否持续打印日志
run_once = input_params.get("runOnce")
run_once = False if run_once is None else run_once # 是否至少执行一次
retry_period = input_params.get("retryPeriod")
retry_period = 0 if retry_period is None else retry_period # 循环间隔(毫秒)
print_continuously = input_params.get("printContinuously")
print_continuously = False if print_continuously is None else print_continuously # 是否持续打印日志
print("loop_condition", loop_condition, "=============================================")
print("run_once", run_once, "=============================================")
print("retry_period", retry_period, "=============================================")
print("print_continuously", print_continuously, "=============================================")
# 迭代计数器
iterations = 0
@ -282,7 +290,7 @@ class WhileBlockHandler(BlockHandler):
# 这确保了其他请求能够得到处理
if iterations % 10 == 0:
import asyncio
await asyncio.sleep(0.1) # 释放事件循环的最小时间
await asyncio.sleep(0.2) # 释放事件循环的最小时间
# 执行循环体
if print_continuously or iterations == 0:
@ -615,7 +623,6 @@ class IterateListBlockHandler(BlockHandler):
try:
# 获取数组参数
array_data = input_params.get("list", [])
# 如果不是数组,尝试转换
if not isinstance(array_data, list):
import json
@ -667,6 +674,9 @@ class IterateListBlockHandler(BlockHandler):
import asyncio
await asyncio.sleep(0.1) # 释放事件循环的最小时间
if context.get_variable("__RETURN__", False):
logger.info(f"检测到返回信号,提前结束重复执行")
break
# 设置循环变量,使子组件可以访问当前项和索引
context.set_variable("index", index)
context.set_variable("item", item)
@ -674,7 +684,6 @@ class IterateListBlockHandler(BlockHandler):
# 执行循环体
loop_result = await executor.execute_children(block, "default")
# 收集结果
results.append({
"index": index,
@ -749,8 +758,6 @@ class IterateListBlockHandler(BlockHandler):
"count": len(results)
}
}
# 记录执行结果
await self._record_task_log(block, result, context)
return result
except Exception as e:
result = {
@ -974,7 +981,7 @@ class SerialFlowBlockHandler(BlockHandler):
final_result = {
"success": True,
"message": "串行执行完成",
"output": result.get("output", {})
# "output": result.get("output", {})
}
# 记录执行结果
await self._record_task_log(block, final_result, context)
@ -1013,7 +1020,6 @@ class ParallelFlowBlockHandler(BlockHandler):
result = {
"success": True,
"message": "没有子块需要执行",
"output": {}
}
# 记录执行结果
await self._record_task_log(block, result, context)
@ -1086,7 +1092,7 @@ class ParallelFlowBlockHandler(BlockHandler):
"success": all_success,
"message": "并行执行完成" if all_success else "并行执行部分失败",
"output": {
"results": results,
# "results": results,
"allSuccess": all_success
}
}
@ -1119,13 +1125,12 @@ class RepeatNumBlockHandler(BlockHandler):
# 获取执行次数
num = int(input_params.get("num", 1))
# 限制最大执行次数
num = min(num, 1000)
# num = min(num, 1000)
if num <= 0:
if num <= 0 or num > 1000:
result = {
"success": True,
"message": "执行次数必须大于0",
"output": {}
"success": False,
"message": "执行次数必须大于0且小于1000",
}
# 记录执行结果
await self._record_task_log(block, result, context)
@ -1166,8 +1171,8 @@ class RepeatNumBlockHandler(BlockHandler):
await asyncio.sleep(0.1) # 释放事件循环的最小时间
# 设置当前索引
context.set_variable("currentIndex", i)
# context.set_variable("index", i)
context.set_block_output(block.get("name"), {"index": i})
# 执行子块
result = await executor.execute_children(block, "default")
results.append({
@ -1182,7 +1187,7 @@ class RepeatNumBlockHandler(BlockHandler):
"success": False,
"message": f"重复执行在第 {i+1} 次时失败",
"output": {
"results": results,
# "results": results,
"iterations": i + 1,
"totalIterations": num,
"failedAt": i
@ -1200,7 +1205,6 @@ class RepeatNumBlockHandler(BlockHandler):
"success": True,
"message": f"重复执行第{i+1}次检测到break信号提前结束执行",
"output": {
"results": results,
"iterations": i + 1,
"totalIterations": num,
"stoppedByBreak": True
@ -1221,7 +1225,6 @@ class RepeatNumBlockHandler(BlockHandler):
"success": False,
"message": f"完成分支执行失败: {complete_result.get('message')}",
"output": {
"results": results,
"iterations": len(results),
"totalIterations": num
}
@ -1243,7 +1246,6 @@ class RepeatNumBlockHandler(BlockHandler):
"success": False,
"message": f"完成分支执行失败: {complete_result.get('message')}",
"output": {
"results": results,
"iterations": len(results),
"totalIterations": num
}
@ -1256,7 +1258,6 @@ class RepeatNumBlockHandler(BlockHandler):
"success": True,
"message": f"重复执行成功,共执行 {len(results)}",
"output": {
"results": results,
"iterations": len(results),
"totalIterations": num
}

View File

@ -360,7 +360,7 @@ class AgvOperationBlockHandler(RobotBlockHandler):
try:
# 获取关键参数用于验证
target_site_label = input_params.get("targetSiteLabel")
script_name = input_params.get("scriptName")
script_name = input_params.get("task")
# 参数检查
if not target_site_label:
result = {

View File

@ -314,6 +314,14 @@ class ScriptBlockHandler(BlockHandler):
elif isinstance(function_args, list):
args = function_args
# 如果function_args是其他类型将其作为单个位置参数传递
elif isinstance(function_args, str):
eval_result = eval(function_args)
if isinstance(eval_result, dict):
kwargs = eval_result
elif isinstance(eval_result, list):
args = eval_result
else:
args = [eval_result]
elif function_args is not None:
args = [function_args]

View File

@ -195,7 +195,7 @@ class GetCacheDataBlockHandler(BlockHandler):
result = {
"success": True,
"message": f"获取缓存数据成功: {key}",
"data": {"value": value}
"output": {"value": value}
}
# 记录执行结果
@ -300,13 +300,11 @@ class SetTaskVariableBlockHandler(BlockHandler):
# 获取任务记录ID
task_record_id = context.task_record_id
if not task_record_id:
return
# 将任务变量转换为JSON字符串
variables_json = json.dumps(context.variables, ensure_ascii=False)
# 更新数据库记录
async with get_async_session() as session:
stmt = update(VWEDTaskRecord).where(

View File

@ -200,11 +200,9 @@ class TaskExecutor:
# 获取根块
task_detail = json.loads(self.task_def.detail)
root_block = task_detail.get("rootBlock", {})
# 更新任务状态为执行中
async with get_async_session() as session:
await self._update_task_status(session, TaskStatus.RUNNING, "任务执行中", task_detail=self.task_def.detail)
# 执行根块,增加超时控制
try:
# 直接执行块,不添加超时控制

View File

@ -116,18 +116,13 @@ async def create_task(task_record_id: str, task_name: str, is_periodic: bool, pr
url = f"{TFApiConfig.BASE_URL}{TFApiConfig.CREATE_TASK_PATH}"
# 构建请求头
headers = {}
if token:
headers[TFApiConfig.TOKEN_HEADER] = token
headers["x-tenant-id"] = "1000"
logger.debug(f"使用认证令牌调用接口,令牌头: {TFApiConfig.TOKEN_HEADER}")
else:
# headers["Authorization"] = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NDU3ODgwMzksInVzZXJuYW1lIjoiYWRtaW4ifQ.QmFrhe9nq8jdNRVtZYo-QK-31hS7AMAwMjwy8EGERQQ"
headers["x-access-token"] = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NDYyNzUwODEsInVzZXJuYW1lIjoiYWRtaW4ifQ.iSp50PmKX1NmscNhVtSRtFwLGpuU1z71zvp0ki4BbTY"
headers["x-tenant-id"] = "1000"
headers[TFApiConfig.TOKEN_HEADER] = token
headers["x-tenant-id"] = "1000"
logger.debug(f"使用认证令牌调用接口,令牌头: {TFApiConfig.TOKEN_HEADER}")
try:
logger.info(f"正在同步创建任务到天风系统: {task_record_id}")
logger.debug(f"创建任务请求参数: {request_data.model_dump_json()}")
async with aiohttp.ClientSession() as session:
async with session.post(
url,
@ -184,12 +179,8 @@ async def create_choose_amr_task(task_id: str, key_station_name: str, amr_name:
# 构建请求头
headers = {}
if token:
headers[TFApiConfig.TOKEN_HEADER] = token
headers["x-tenant-id"] = "1000"
else:
headers["x-access-token"] = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NDYyNzUwODEsInVzZXJuYW1lIjoiYWRtaW4ifQ.iSp50PmKX1NmscNhVtSRtFwLGpuU1z71zvp0ki4BbTY"
headers["x-tenant-id"] = "1000"
headers[TFApiConfig.TOKEN_HEADER] = token
headers["x-tenant-id"] = "1000"
try:
logger.info(f"正在创建选择AMR任务: {task_id}, 站点: {key_station_name}")
@ -203,6 +194,7 @@ async def create_choose_amr_task(task_id: str, key_station_name: str, amr_name:
) as response:
# 读取响应内容
response_text = await response.json()
print("response_text",response_text)
# 尝试解析JSON
try:
if response_text.get("success", False):
@ -243,12 +235,8 @@ async def add_action(task_id: str, station_name: str, action: str, token: str =
# 构建请求头
headers = {}
if token:
headers[TFApiConfig.TOKEN_HEADER] = token
headers["x-tenant-id"] = "1000"
else:
headers["x-access-token"] = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NDYyNzUwODEsInVzZXJuYW1lIjoiYWRtaW4ifQ.iSp50PmKX1NmscNhVtSRtFwLGpuU1z71zvp0ki4BbTY"
headers["x-tenant-id"] = "1000"
headers[TFApiConfig.TOKEN_HEADER] = token
headers["x-tenant-id"] = "1000"
try:
logger.info(f"正在为任务添加动作: {task_id}, 站点: {station_name}, 动作: {action}")
@ -295,12 +283,8 @@ async def closure_task(task_id: str, token: str = None) -> Optional[ApiResponse]
url = f"{TFApiConfig.BASE_URL}{TFApiConfig.CLOSURE_TASK_PATH.format(task_id)}"
# 构建请求头
headers = {}
if token:
headers[TFApiConfig.TOKEN_HEADER] = token
headers["x-tenant-id"] = "1000"
else:
headers["x-access-token"] = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NDYyNzUwODEsInVzZXJuYW1lIjoiYWRtaW4ifQ.iSp50PmKX1NmscNhVtSRtFwLGpuU1z71zvp0ki4BbTY"
headers["x-tenant-id"] = "1000"
headers[TFApiConfig.TOKEN_HEADER] = token
headers["x-tenant-id"] = "1000"
try:
logger.info(f"正在封口任务: {task_id}")
@ -344,12 +328,8 @@ async def get_task_block_detail(task_block_id: str, token: str = None) -> Option
# 构建请求头
headers = {}
if token:
headers[TFApiConfig.TOKEN_HEADER] = token
headers["x-tenant-id"] = "1000"
else:
headers["x-access-token"] = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NDYyNzUwODEsInVzZXJuYW1lIjoiYWRtaW4ifQ.iSp50PmKX1NmscNhVtSRtFwLGpuU1z71zvp0ki4BbTY"
headers["x-tenant-id"] = "1000"
headers[TFApiConfig.TOKEN_HEADER] = token
headers["x-tenant-id"] = "1000"
try:
logger.info(f"正在获取任务块详情: {task_block_id}")
@ -397,12 +377,9 @@ async def get_task_block_action_detail(task_block_id: str, token: str = None) ->
# 构建请求头
headers = {}
if token:
headers[TFApiConfig.TOKEN_HEADER] = token
headers["x-tenant-id"] = "1000"
else:
headers["x-access-token"] = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NDYyNzUwODEsInVzZXJuYW1lIjoiYWRtaW4ifQ.iSp50PmKX1NmscNhVtSRtFwLGpuU1z71zvp0ki4BbTY"
headers["x-tenant-id"] = "1000"
headers[TFApiConfig.TOKEN_HEADER] = token
headers["x-tenant-id"] = "1000"
try:
logger.info(f"正在获取任务块动作详情: {task_block_id}")
@ -605,6 +582,7 @@ async def set_task_completed(task_id: str, token: str = None) -> Optional[ApiRes
) as response:
# 读取响应内容
response_text = await response.json()
print("response_text::", response_text, "============================================")
# 尝试解析JSON
try:
if response_text.get("success", False):

View File

@ -385,6 +385,7 @@ class TaskEditService:
await session.commit()
logger.info(f"更新任务定义状态为运行中: {run_request.taskId}")
# 3. 立即执行一次任务
result = await scheduler.run_task(
task_def_id=run_request.taskId,
params=params,
@ -416,7 +417,6 @@ class TaskEditService:
else:
# 普通任务处理流程
logger.info(f"启动普通任务: {run_request.taskId}")
# 4. 更新任务定义状态为运行中(1)
async with get_async_session() as session:
from sqlalchemy import update
await session.execute(
@ -425,7 +425,6 @@ class TaskEditService:
.values(user_token=tf_api_token)
)
await session.commit()
# 执行一次性任务
result = await scheduler.run_task(
task_def_id=run_request.taskId,
params=params,
@ -716,68 +715,24 @@ class TaskEditService:
async with get_async_session() as session:
# 查询任务定义
result = await session.execute(
select(VWEDTaskDef).where(VWEDTaskDef.id == task_id)
select(VWEDTaskRecord).where(VWEDTaskRecord.id == task_id)
)
task_def = result.scalars().first()
if not task_def:
logger.error(f"任务不存在: {task_id}")
return {"inputParams": [], "taskInfo": None, "paramTypes": []}
return {"success": False, "message": "任务不存在"}
# 解析详情JSON
detail = json.loads(task_def.detail) if task_def.detail else {}
# 获取输入参数
input_params = detail.get("inputParams", [])
# 获取任务基本信息
task_info = {
"id": task_def.id,
"label": task_def.label,
"version": task_def.version,
"remark": task_def.remark,
"templateName": task_def.template_name
}
# 获取可用的参数类型与get_input_param_types相同的逻辑
param_types = [
{
"code": "STRING",
"label": "字符串",
"description": "文本类型数据"
},
{
"code": "BOOLEAN",
"label": "布尔",
"description": "布尔类型值为true或false"
},
{
"code": "INTEGER",
"label": "整数",
"description": "整数类型"
},
{
"code": "FLOAT",
"label": "浮点数",
"description": "浮点数类型"
},
{
"code": "OBJECT",
"label": "JSON对象",
"description": "JSON对象类型{\"key\":\"value\"}"
},
{
"code": "ARRAY",
"label": "JSON数组",
"description": "JSON数组类型如[\"item1\",\"item2\"]"
}
]
input_params = json.loads(task_def.input_params) if task_def.input_params else {}
# 返回完整信息
return {
"inputParams": input_params,
"taskInfo": task_info,
"paramTypes": param_types
"success": True,
"message": "获取任务输入参数配置成功",
"data": {
"inputParams": input_params
}
}
except Exception as e:
logger.error(f"获取任务输入参数配置失败: {str(e)}")
@ -880,121 +835,6 @@ class TaskEditService:
# 出错时默认为有变化,以确保数据安全
return {"changed": True, "error": str(e)}
@staticmethod
async def stop_task_def(task_def_id: str) -> Dict[str, Any]:
"""
停止指定任务定义下的所有运行任务实例同时禁用定时任务
Args:
task_def_id: 任务定义ID
Returns:
字典包含停止结果信息
- success: 是否操作成功
- message: 操作结果消息
- is_periodic: 是否为定时任务
- total_running: 运行中的任务总数
- stopped_count: 成功停止的任务数量
- failed_count: 停止失败的任务数量
- failed_tasks: 停止失败的任务记录ID列表
"""
# 导入增强版调度器
from services.enhanced_scheduler import scheduler
try:
async with get_async_session() as session:
# 查询任务定义
result = await session.execute(
select(VWEDTaskDef).where(VWEDTaskDef.id == task_def_id)
)
task_def = result.scalars().first()
if not task_def:
return {
"success": False,
"message": f"未找到任务定义: {task_def_id}"
}
is_periodic = task_def.periodic_task == PeriodicTaskStatus.PERIODIC
# 初始化计数器
total_running = 0
stopped_count = 0
failed_count = 0
failed_tasks = []
# 如果是定时任务则禁用
if is_periodic and task_def.if_enable == EnableStatus.ENABLED:
# 更新任务定义状态为禁用
await session.execute(
update(VWEDTaskDef)
.where(VWEDTaskDef.id == task_def_id)
.values(if_enable=EnableStatus.DISABLED)
)
await session.commit()
# 通知调度器
update_result = await scheduler.update_periodic_task(task_def_id, enable=False)
# 将定时任务的禁用也计入停止任务的数量
total_running += 1
if update_result.get("success", True): # 假设通知调度器成功,除非明确返回失败
stopped_count += 1
else:
failed_count += 1
failed_tasks.append({
"taskRecordId": "periodic_" + task_def_id,
"reason": update_result.get("message", "禁用定时任务失败")
})
# 查找所有正在运行的任务记录
running_tasks_query = await session.execute(
select(VWEDTaskRecord)
.where(
VWEDTaskRecord.def_id == task_def_id,
VWEDTaskRecord.status == TaskStatus.RUNNING # 执行中状态码
)
)
running_tasks = running_tasks_query.scalars().all()
# 更新总计数
total_running += len(running_tasks)
# 取消所有运行中的任务
for task_record in running_tasks:
cancel_result = await scheduler.cancel_task(task_record.id)
if cancel_result.get("success", False):
stopped_count += 1
else:
failed_count += 1
failed_tasks.append({
"taskRecordId": task_record.id,
"reason": cancel_result.get("message", "未知原因")
})
# 更新任务定义状态为已结束(0)
await session.execute(
update(VWEDTaskDef)
.where(VWEDTaskDef.id == task_def_id)
.values(status=TaskStatusEnum.PENDING)
)
await session.commit()
return {
"success": True,
"message": "操作完成",
"is_periodic": is_periodic,
"total_running": total_running,
"stopped_count": stopped_count,
"failed_count": failed_count,
"failed_tasks": failed_tasks
}
except Exception as e:
return {
"success": False,
"message": f"停止任务失败: {str(e)}"
}
@staticmethod
async def check_running_task_for_device(task_id: str, device_id: str) -> Dict[str, Any]:

View File

@ -0,0 +1,394 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
任务运行记录服务模块
提供任务运行记录相关的服务方法
"""
import json
from typing import Dict, List, Any, Optional
from sqlalchemy import select, and_
import datetime
from data.models.blockrecord import VWEDBlockRecord
from data.models.taskrecord import VWEDTaskRecord
from data.models.tasklog import VWEDTaskLog
from data.session import get_async_session
from utils.logger import get_logger
from data.enum.task_block_record_enum import TaskBlockRecordStatus
# 设置日志
logger = get_logger("service.task_record_service")
class TaskRecordService:
"""
任务运行记录服务类
提供与任务运行记录相关的方法
"""
@staticmethod
async def get_task_blocks(task_record_id: str) -> Dict[str, Any]:
"""
获取指定任务记录下的所有块运行情况
Args:
task_record_id: 任务记录ID
Returns:
Dict: 包含块运行情况的字典
"""
try:
async with get_async_session() as session:
# 构建查询语句
query = select(VWEDBlockRecord).where(
VWEDBlockRecord.task_record_id == task_record_id
).order_by(VWEDBlockRecord.started_on)
# 执行查询
result = await session.execute(query)
blocks = result.scalars().all()
if not blocks:
return {
"success": True,
"message": f"未找到任务记录 {task_record_id} 的块运行情况",
"data": []
}
# 转换为字典列表
block_list = []
for block in blocks:
block_dict = {
"id": block.id,
"block_name": block.block_name,
"block_id": block.block_id,
"status": block.status,
"started_on": block.started_on.isoformat() if block.started_on else None,
"ended_on": block.ended_on.isoformat() if block.ended_on else None,
"ended_reason": block.ended_reason,
"block_execute_name": block.block_execute_name,
"block_input_params_value": json.loads(block.block_input_params_value) if block.block_input_params_value else None,
"block_out_params_value": json.loads(block.block_out_params_value) if block.block_out_params_value else None,
"remark": block.remark
}
block_list.append(block_dict)
return {
"success": True,
"message": f"成功获取任务记录 {task_record_id} 的块运行情况",
"data": block_list
}
except Exception as e:
logger.error(f"获取任务块运行情况失败: {str(e)}")
return {
"success": False,
"message": f"获取任务块运行情况失败: {str(e)}",
"data": []
}
@staticmethod
async def get_block_detail(block_record_id: str) -> Dict[str, Any]:
"""
获取指定块记录的详细信息
Args:
block_record_id: 块记录ID
Returns:
Dict: 包含块记录详细信息的字典
"""
try:
async with get_async_session() as session:
# 构建查询语句
query = select(VWEDBlockRecord).where(
VWEDBlockRecord.id == block_record_id
)
# 执行查询
result = await session.execute(query)
block = result.scalars().first()
if not block:
return {
"success": False,
"message": f"未找到ID为 {block_record_id} 的块记录",
"data": None
}
# 转换为字典
block_dict = {
"id": block.id,
"block_name": block.block_name,
"block_id": block.block_id,
"block_config_id": block.block_config_id,
"block_input_params": json.loads(block.block_input_params) if block.block_input_params else None,
"block_input_params_value": json.loads(block.block_input_params_value) if block.block_input_params_value else None,
"block_out_params_value": json.loads(block.block_out_params_value) if block.block_out_params_value else None,
"block_internal_variables": json.loads(block.block_internal_variables) if block.block_internal_variables else None,
"block_execute_name": block.block_execute_name,
"task_id": block.task_id,
"task_record_id": block.task_record_id,
"started_on": block.started_on.isoformat() if block.started_on else None,
"ended_on": block.ended_on.isoformat() if block.ended_on else None,
"ended_reason": block.ended_reason,
"status": block.status,
"ctrl_status": block.ctrl_status,
"input_params": json.loads(block.input_params) if block.input_params else None,
"internal_variables": json.loads(block.internal_variables) if block.internal_variables else None,
"output_params": json.loads(block.output_params) if block.output_params else None,
"version": block.version,
"remark": block.remark
}
return {
"success": True,
"message": "成功获取块记录详情",
"data": block_dict
}
except Exception as e:
logger.error(f"获取块记录详情失败: {str(e)}")
return {
"success": False,
"message": f"获取块记录详情失败: {str(e)}",
"data": None
}
@staticmethod
async def stop_task_record(task_record_id: str) -> Dict[str, Any]:
"""
停止指定任务记录下的所有运行任务实例同时禁用定时任务
Args:
task_record_id: 任务记录ID
Returns:
Dict: 包含停止结果的响应
"""
# 导入增强版调度器
from services.enhanced_scheduler import scheduler
from data.enum.task_record_enum import TaskStatus
from datetime import datetime
try:
async with get_async_session() as session:
# 查找所有正在运行的任务记录
running_tasks_query = await session.execute(
select(VWEDTaskRecord)
.where(
VWEDTaskRecord.id == task_record_id,
VWEDTaskRecord.status == TaskStatus.RUNNING # 执行中状态码
)
)
running_tasks = running_tasks_query.scalars().first()
if not running_tasks:
return {
"success": True,
"message": "任务记录中没有运行中的任务"
}
# 取消所有运行中的任务
cancel_result = await scheduler.cancel_task(task_record_id)
if cancel_result.get("success", False):
running_tasks.status = TaskStatus.CANCELED
running_tasks.ended_on = datetime.now()
running_tasks.ended_reason = "任务终止"
await session.commit()
return {
"success": True,
"message": "任务终止成功",
"data": {
"task_record_id": task_record_id,
"status": running_tasks.status,
"ended_on": running_tasks.ended_on,
"ended_reason": running_tasks.ended_reason
}
}
else:
return {
"success": False,
"message": "任务终止失败",
"data": cancel_result
}
except Exception as e:
logger.error(f"任务记录终止失败: {str(e)}")
return {
"success": False,
"message": f"任务记录终止失败: {str(e)}"
}
@staticmethod
async def get_block_results(task_record_id: str) -> Dict[str, Any]:
"""
获取指定任务记录的执行结果
Args:
task_record_id: 任务记录ID
Returns:
Dict: 包含执行结果的响应
"""
try:
async with get_async_session() as session:
# 构建查询语句
query = select(VWEDBlockRecord).where(
VWEDBlockRecord.task_record_id == task_record_id
)
result = await session.execute(query)
blocks = result.scalars().all()
block_results = []
# 使用集合记录已处理的block_name用于去重
processed_block_names = set()
for block in blocks:
# 如果block_name已经处理过则跳过
if block.status != TaskBlockRecordStatus.SUCCESS:
if block.block_name == "-1":
block_results.append({"created_at":block.created_at, "context":"["+block.block_execute_name+"] "+block.task_record_id, "status":block.status})
continue
else:
block_results.append({"created_at":block.created_at, "context":"["+block.block_execute_name+"] "+block.ended_reason, "status":block.status})
else:
if block.block_name == "-1":
block_results.append({"created_at":block.created_at, "context":"["+block.block_execute_name+"] "+block.task_record_id, "status":block.status})
continue
task_logs_query = select(VWEDTaskLog).where(
VWEDTaskLog.task_record_id == task_record_id,
VWEDTaskLog.task_block_id == block.block_name
)
if block.block_name in processed_block_names:
continue
# 记录已处理的block_name
processed_block_names.add(block.block_name)
task_logs = await session.execute(task_logs_query)
# task_logs = task_logs.scalars().all()
task_logs = task_logs.scalars().all()
if task_logs and len(task_logs) == 1:
messages = json.loads(task_logs[0].message)
message = messages.get("message", "")
output = messages.get("output", "")
if output and str(output.get("message", "")):
block_results.append({"created_at":block.created_at, "context":"["+block.block_execute_name+"] "+message+"@"+str(output.get("message", "")), "status":block.status})
elif output:
block_results.append({"created_at":block.created_at, "context":"["+block.block_execute_name+"] "+message+"@"+str(output), "status":block.status})
else:
block_results.append({"created_at":block.created_at, "context":"["+block.block_execute_name+"] "+message, "status":block.status})
elif task_logs and len(task_logs) > 1:
for task_log in task_logs:
messages = json.loads(task_log.message)
message = messages.get("message", "")
output = messages.get("output", "")
if output and str(output.get("message", "")):
block_results.append({"created_at":block.created_at, "context":"["+block.block_execute_name+"] "+message+"@"+str(output.get("message", "")), "status":block.status})
elif output:
block_results.append({"created_at":block.created_at, "context":"["+block.block_execute_name+"] "+message+"@"+str(output), "status":block.status})
else:
block_results.append({"created_at":block.created_at, "context":"["+block.block_execute_name+"] "+message, "status":block.status})
else:
block_results.append({"created_at":block.created_at, "context":"["+block.block_execute_name+"] "+block.ended_reason, "status":block.status})
logger.warning(f"任务记录 {task_record_id} 的块 {block.block_name} 没有日志")
if not blocks:
return {
"success": True,
"message": f"未找到任务记录 {task_record_id} 的块运行情况",
"data": []
}
return {
"success": True,
"message": "成功获取任务记录执行结果",
"data": block_results
}
except Exception as e:
logger.error(f"获取任务记录执行结果失败: {str(e)}")
return {
"success": False,
"message": f"获取任务记录执行结果失败: {str(e)}"
}
@staticmethod
async def get_task_record_detail(task_record_id: str) -> Dict[str, Any]:
"""
获取指定任务记录的详细信息
Args:
task_record_id: 任务记录ID
Returns:
Dict: 包含任务记录详细信息的字典
"""
try:
async with get_async_session() as session:
# 构建查询语句
query = select(VWEDTaskRecord).where(
VWEDTaskRecord.id == task_record_id
)
# 执行查询
result = await session.execute(query)
task_record = result.scalars().first()
if not task_record:
return {
"success": False,
"message": f"未找到ID为 {task_record_id} 的任务记录",
"data": None
}
# 计算执行时长(如果任务已结束)
execution_time = None
if task_record.ended_on and task_record.first_executor_time:
time_diff = task_record.ended_on - task_record.first_executor_time
execution_time = int(time_diff.total_seconds() * 1000) # 转换为毫秒
elif task_record.executor_time:
execution_time = task_record.executor_time
# 转换为字典
task_dict = {
"id": task_record.id,
"task_id": task_record.def_id,
"task_name": task_record.def_label,
"task_version": task_record.def_version,
"status": task_record.status,
"input_params": json.loads(task_record.input_params) if task_record.input_params else None,
"started_on": task_record.first_executor_time.isoformat() if task_record.first_executor_time else None,
"ended_on": task_record.ended_on.isoformat() if task_record.ended_on else None,
"ended_reason": task_record.ended_reason,
"execution_time": execution_time,
"created_at": task_record.created_at.isoformat() if task_record.created_at else None,
"updated_at": task_record.updated_at.isoformat() if task_record.updated_at else None,
"agv_id": task_record.agv_id,
"parent_task_record_id": task_record.parent_task_record_id,
"root_task_record_id": task_record.root_task_record_id,
"state_description": task_record.state_description,
"if_have_child_task": bool(task_record.if_have_child_task) if task_record.if_have_child_task is not None else None,
"periodic_task": task_record.periodic_task,
"priority": task_record.priority,
"work_stations": task_record.work_stations,
"work_types": task_record.work_types,
"variables": json.loads(task_record.variables) if task_record.variables else None,
"source_type": task_record.source_type,
"source_system": task_record.source_system,
"source_user": task_record.source_user,
"source_device": task_record.source_device,
"source_ip": task_record.source_ip,
"source_time": task_record.source_time.isoformat() if task_record.source_time else None,
"source_client_info": task_record.source_client_info,
"source_remarks": task_record.source_remarks
}
return {
"success": True,
"message": "成功获取任务记录详情",
"data": task_dict
}
except Exception as e:
logger.error(f"获取任务记录详情失败: {str(e)}")
return {
"success": False,
"message": f"获取任务记录详情失败: {str(e)}",
"data": None
}

View File

@ -18,7 +18,9 @@ from data.models.taskrecord import VWEDTaskRecord
from data.models.tasktemplate import VWEDTaskTemplate
from data.enum.task_def_enum import TaskTypeEnum, TaskStatusEnum, EnableStatus, PeriodicTaskStatus
from utils.logger import get_logger
from data.session import get_async_session
from sqlalchemy import select, update
from data.enum.task_record_enum import TaskStatus
# 设置日志
logger = get_logger("services.task_service")
@ -272,9 +274,11 @@ class TaskService:
Optional[Dict[str, Any]]: 任务信息如果不存在则返回None
"""
try:
# 显式启动事务
task = db.query(VWEDTaskDef).filter(VWEDTaskDef.id == task_id).first()
if not task:
return None
# 格式化任务信息
task_info = {
"id": task.id,
@ -292,9 +296,13 @@ class TaskService:
"remark": task.remark,
}
# 提交事务以释放数据库锁
db.commit()
return task_info
except Exception as e:
# 发生异常时回滚事务
db.rollback()
logger.error(f"获取任务失败: {str(e)}")
raise Exception(f"获取任务失败: {str(e)}")
@ -437,4 +445,120 @@ class TaskService:
except Exception as e:
db.rollback()
logger.error(f"导入任务失败: {str(e)}")
raise Exception(f"导入任务失败: {str(e)}")
raise Exception(f"导入任务失败: {str(e)}")
@staticmethod
async def stop_task_def(task_def_id: str) -> Dict[str, Any]:
"""
停止指定任务定义下的所有运行任务实例同时禁用定时任务
Args:
task_def_id: 任务定义ID
Returns:
字典包含停止结果信息
- success: 是否操作成功
- message: 操作结果消息
- is_periodic: 是否为定时任务
- total_running: 运行中的任务总数
- stopped_count: 成功停止的任务数量
- failed_count: 停止失败的任务数量
- failed_tasks: 停止失败的任务记录ID列表
"""
# 导入增强版调度器
from services.enhanced_scheduler import scheduler
try:
async with get_async_session() as session:
# 查询任务定义
result = await session.execute(
select(VWEDTaskDef).where(VWEDTaskDef.id == task_def_id)
)
task_def = result.scalars().first()
if not task_def:
return {
"success": False,
"message": f"未找到任务定义: {task_def_id}"
}
is_periodic = task_def.periodic_task == PeriodicTaskStatus.PERIODIC
# 初始化计数器
total_running = 0
stopped_count = 0
failed_count = 0
failed_tasks = []
# 如果是定时任务则禁用
if is_periodic and task_def.if_enable == EnableStatus.ENABLED:
# 更新任务定义状态为禁用
await session.execute(
update(VWEDTaskDef)
.where(VWEDTaskDef.id == task_def_id)
.values(if_enable=EnableStatus.DISABLED)
)
await session.commit()
# 通知调度器
update_result = await scheduler.update_periodic_task(task_def_id, enable=False)
# 将定时任务的禁用也计入停止任务的数量
total_running += 1
if update_result.get("success", True): # 假设通知调度器成功,除非明确返回失败
stopped_count += 1
else:
failed_count += 1
failed_tasks.append({
"taskRecordId": "periodic_" + task_def_id,
"reason": update_result.get("message", "禁用定时任务失败")
})
# 查找所有正在运行的任务记录
running_tasks_query = await session.execute(
select(VWEDTaskRecord)
.where(
VWEDTaskRecord.def_id == task_def_id,
VWEDTaskRecord.status == TaskStatus.RUNNING # 执行中状态码
)
)
running_tasks = running_tasks_query.scalars().all()
# 更新总计数
total_running += len(running_tasks)
# 取消所有运行中的任务
for task_record in running_tasks:
cancel_result = await scheduler.cancel_task(task_record.id)
if cancel_result.get("success", False):
stopped_count += 1
else:
failed_count += 1
failed_tasks.append({
"taskRecordId": task_record.id,
"reason": cancel_result.get("message", "未知原因")
})
# 更新任务定义状态为已结束(0)
await session.execute(
update(VWEDTaskDef)
.where(VWEDTaskDef.id == task_def_id)
.values(status=TaskStatusEnum.PENDING)
)
await session.commit()
return {
"success": True,
"message": "操作完成",
"is_periodic": is_periodic,
"total_running": total_running,
"stopped_count": stopped_count,
"failed_count": failed_count,
"failed_tasks": failed_tasks
}
except Exception as e:
return {
"success": False,
"message": f"停止任务失败: {str(e)}"
}

View File

@ -1 +1,3 @@
import json
print(json.loads('[{"1": 2}]'))