update bug
This commit is contained in:
parent
0189b1f0b9
commit
b684132a73
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
@ -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
|
||||
}
|
||||
```
|
||||
|
@ -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. 运行任务
|
||||
|
||||
#### 接口描述
|
||||
|
698
VWED任务模块接口文档/任务记录接口文档.md
Normal file
698
VWED任务模块接口文档/任务记录接口文档.md
Normal 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": "测试任务"
|
||||
}
|
||||
}
|
||||
```
|
@ -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_config(Modbus配置表)
|
||||
#### modbus_config(Modbus配置表)
|
||||
- **功能**:存储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
3
app.py
@ -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():
|
||||
|
Binary file not shown.
@ -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": "",
|
||||
|
@ -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:
|
||||
|
@ -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.
Binary file not shown.
BIN
data/models/__pycache__/calldevice.cpython-312.pyc
Normal file
BIN
data/models/__pycache__/calldevice.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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
89
data/models/calldevice.py
Normal 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}')>"
|
@ -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')
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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}')>"
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
68
data/task_backups/tasks_backup_20250506_142813.json
Normal file
68
data/task_backups/tasks_backup_20250506_142813.json
Normal 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"
|
||||
}
|
||||
}
|
0
data/task_backups/tasks_backup_20250506_143738.json
Normal file
0
data/task_backups/tasks_backup_20250506_143738.json
Normal file
35
data/task_backups/tasks_backup_20250506_143852.json
Normal file
35
data/task_backups/tasks_backup_20250506_143852.json
Normal 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"
|
||||
}
|
||||
}
|
0
data/task_backups/tasks_backup_20250506_161054.json
Normal file
0
data/task_backups/tasks_backup_20250506_161054.json
Normal file
35
data/task_backups/tasks_backup_20250506_161630.json
Normal file
35
data/task_backups/tasks_backup_20250506_161630.json
Normal 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
70087
logs/app.log
File diff suppressed because it is too large
Load Diff
153988
logs/app.log.1
153988
logs/app.log.1
File diff suppressed because it is too large
Load Diff
165016
logs/app.log.2
165016
logs/app.log.2
File diff suppressed because it is too large
Load Diff
173075
logs/app.log.3
173075
logs/app.log.3
File diff suppressed because it is too large
Load Diff
164219
logs/app.log.4
164219
logs/app.log.4
File diff suppressed because it is too large
Load Diff
153027
logs/app.log.5
153027
logs/app.log.5
File diff suppressed because it is too large
Load Diff
47064
logs/error.log
47064
logs/error.log
File diff suppressed because it is too large
Load Diff
19
routes/__init__.py
Normal file
19
routes/__init__.py
Normal 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路由
|
||||
# # ... 注册其他路由
|
BIN
routes/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
routes/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
routes/__pycache__/task_record_api.cpython-312.pyc
Normal file
BIN
routes/__pycache__/task_record_api.cpython-312.pyc
Normal file
Binary file not shown.
BIN
routes/__pycache__/task_record_service.cpython-312.pyc
Normal file
BIN
routes/__pycache__/task_record_service.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
@ -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
|
@ -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)
|
||||
|
@ -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
137
routes/task_record_api.py
Normal 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)
|
Binary file not shown.
Binary file not shown.
BIN
services/__pycache__/task_record_service.cpython-312.pyc
Normal file
BIN
services/__pycache__/task_record_service.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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]:
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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:
|
||||
"""
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -62,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
|
||||
)
|
||||
|
@ -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})
|
||||
|
@ -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}"
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 = {
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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:
|
||||
# 直接执行块,不添加超时控制
|
||||
|
@ -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):
|
||||
|
@ -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]:
|
||||
|
394
services/task_record_service.py
Normal file
394
services/task_record_service.py
Normal 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
|
||||
}
|
||||
|
@ -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)}"
|
||||
}
|
@ -1 +1,3 @@
|
||||
import json
|
||||
|
||||
print(json.loads('[{"1": 2}]'))
|
Loading…
x
Reference in New Issue
Block a user