Compare commits

..

2 Commits

Author SHA1 Message Date
7ef023842b 增加库位库区模块 2025-08-13 15:27:04 +08:00
1185f25fd3 添加其他逻辑 2025-07-30 15:11:59 +08:00
197 changed files with 634801 additions and 67191 deletions

169
CLAUDE.md Normal file
View File

@ -0,0 +1,169 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
VWED Task Module is a FastAPI-based Python system for managing and executing task workflows for AMR (Autonomous Mobile Robot) scheduling systems. The system provides a low-code configuration tool that allows users to design and configure complex robot task flows through a visual interface.
## Development Commands
### Application Startup
```bash
# Primary method - run the main application
python app.py
# Alternative method - using uvicorn directly
uvicorn app:app --host 0.0.0.0 --port 8000 --reload
```
### Database Operations
```bash
# Run database migrations
python scripts/run_migration.py
# Generate new migrations
python scripts/generate_migration.py
# Initialize database
python scripts/init_db.py
```
### Docker Deployment
```bash
# Build and run with Docker
docker build -t vwed-task:latest .
docker run -d -p 8000:8000 --name vwed-task-container vwed-task:latest
# Or use Docker Compose (recommended)
docker-compose up -d
```
## Architecture Overview
The system follows a layered architecture with clear separation of concerns:
### Core Layers
- **API Layer (`routes/`)**: FastAPI REST endpoints for client communication
- **Middleware Layer (`middlewares/`)**: Request logging, error handling, and cross-cutting concerns
- **Business Logic Layer (`services/`)**: Core business logic including task execution and scheduling
- **Data Layer (`data/`)**: ORM models, database session management, and data persistence
- **Component Layer (`components/`)**: Extensible component system for task workflow building
- **Configuration Layer (`config/`)**: System settings, database configuration, and error mappings
### Key Services
- **Task Execution Engine (`services/execution/`)**: Handles task lifecycle and execution
- `task_executor.py`: Main task execution controller
- `block_executor.py`: Individual task block execution
- `task_context.py`: Execution context and variable management
- `handlers/`: Specific handlers for different component types
- **Enhanced Scheduler (`services/enhanced_scheduler/`)**: High-performance async task scheduling
- **Intelligence Layer (`services/intelligence/`)**: AI-powered features (partial implementation)
### Data Models
Key models in `data/models/`:
- `taskdef.py`: Task definition and configuration
- `taskrecord.py`: Task execution records
- `blockrecord.py`: Individual block execution records
- `tasktemplate.py`: Reusable task templates
- `calldevice.py`: Device integration configurations
## Key Features
### Task Management
- Visual task flow designer with drag-and-drop interface
- Version control for task configurations
- Task templates for reusability
- Real-time execution monitoring
### Component System
Components are registered in `config/components/` and include:
- Foundation components (basic operations)
- Robot scheduling components
- HTTP request components
- Storage location management
- Progress tracking components
- Script execution components
### Device Integration
- Modbus protocol support (`modbusconfig.py`)
- Generic device calling interface (`calldevice.py`)
- Extensible communication protocols
## Development Guidelines
### Database Schema Changes
1. Modify models in `data/models/`
2. Generate migration: `python scripts/generate_migration.py`
3. Apply migration: `python scripts/run_migration.py`
### Adding New Components
1. Define component configuration in `config/components/`
2. Implement handler in `services/execution/handlers/`
3. Register component in the component system
### Task Execution Flow
1. Task definition stored in `taskdef` table
2. Execution creates `taskrecord` entry
3. Individual blocks create `blockrecord` entries
4. Context variables managed through `task_context.py`
5. Component handlers execute specific business logic
### Configuration Management
- Environment-specific settings in `config/settings.py`
- Database configuration in `config/database_config.py`
- Error messages centralized in `config/error_messages.py`
## Important Technical Details
### Async Task Scheduling
The system uses a custom enhanced scheduler (`services/enhanced_scheduler/`) that:
- Maintains worker pools for concurrent task execution
- Provides priority-based task queuing
- Handles task persistence and recovery
- Manages worker lifecycle
### Component Architecture
Components follow a registry pattern:
- Each component type has a handler class
- Handlers implement standard execution interface
- Components are configurable through JSON definitions
- Extensible for new component types
### Database Session Management
- Uses SQLAlchemy ORM with session management in `data/session.py`
- Supports both sync and async database operations
- Automatic connection pooling and cleanup
## Testing and Debugging
### Test Files
- Basic tests in `tests/` directory
- Test data and fixtures available
- Integration tests for API endpoints
### Logging
- Centralized logging configuration in `utils/logger.py`
- Application logs stored in `logs/` directory
- Structured logging for debugging task execution
### API Documentation
- Swagger UI available at `http://localhost:8000/docs`
- Comprehensive API documentation in `VWED任务模块接口文档/`
## Common Troubleshooting
### Database Issues
- Check database connection in `config/database_config.py`
- Verify database migrations are up to date
- Review logs in `logs/app.log`
### Task Execution Problems
- Monitor task status through API endpoints
- Check execution logs for specific error messages
- Verify component configurations are correct
### Performance Optimization
- Adjust scheduler worker counts in settings
- Monitor database connection pool usage
- Review task complexity and component efficiency

View File

@ -40,7 +40,7 @@ RUN mkdir -p logs && chmod -R 755 logs
COPY . .
# 暴露端口
EXPOSE 8000
EXPOSE 8001
# 启动命令
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8001"]

View File

@ -565,6 +565,9 @@ ws://your-domain/ws/storage-location-broadcast/{scene_id}
##### 心跳检测
支持两种格式的心跳检测:
**JSON格式心跳**
```json
{
"type": "ping",
@ -572,10 +575,105 @@ ws://your-domain/ws/storage-location-broadcast/{scene_id}
}
```
**字符串格式心跳:**
```
ping
```
#### 服务器消息格式
与库位状态实时推送接口相同,参见上述文档。
##### 心跳响应
对于JSON格式心跳请求服务器返回JSON格式响应
```json
{
"type": "pong",
"timestamp": "2025-06-11T12:00:00.000Z"
}
```
对于字符串格式心跳请求,服务器返回字符串响应:
```
pong
```
#### 连接示例
##### JavaScript客户端示例
```javascript
// 建立WebSocket连接
const sceneId = "your-scene-id";
const wsUrl = `ws://localhost:8000/ws/storage-location-broadcast/${sceneId}`;
const websocket = new WebSocket(wsUrl);
// 连接建立
websocket.onopen = function(event) {
console.log("库位状态广播WebSocket连接已建立");
// 发送JSON格式心跳包
websocket.send(JSON.stringify({
type: "ping",
timestamp: new Date().toISOString()
}));
// 或者发送字符串格式心跳包
// websocket.send("ping");
};
// 接收消息
websocket.onmessage = function(event) {
try {
// 尝试解析JSON格式响应
const data = JSON.parse(event.data);
switch(data.type) {
case "storage_location_update":
console.log("库位状态更新:", data.data);
break;
case "storage_location_status_change":
console.log("库位状态变化:", data.layer_name, data.action);
break;
case "pong":
console.log("JSON格式心跳响应:", data.timestamp);
break;
case "error":
console.error("服务器错误:", data.message);
break;
}
} catch (e) {
// 处理字符串格式响应
if (event.data === "pong") {
console.log("字符串格式心跳响应: pong");
} else {
console.log("收到未知消息:", event.data);
}
}
};
// 定期发送心跳
setInterval(() => {
if (websocket.readyState === WebSocket.OPEN) {
// 可以选择JSON格式或字符串格式
websocket.send("ping"); // 字符串格式
// websocket.send(JSON.stringify({type: "ping", timestamp: new Date().toISOString()})); // JSON格式
}
}, 30000); // 每30秒发送一次心跳
// 连接关闭
websocket.onclose = function(event) {
console.log("库位状态广播WebSocket连接已关闭");
};
// 连接错误
websocket.onerror = function(error) {
console.error("库位状态广播WebSocket连接错误:", error);
};
```
#### 使用场景
1. **监控面板**:多个监控客户端同时监听库位状态变化
@ -636,4 +734,5 @@ ws://your-domain/ws/storage-location-broadcast/{scene_id}
| 版本 | 日期 | 更新内容 |
| --- | --- | --- |
| 1.0.0 | 2025-06-11 | 初始版本,支持任务执行结果实时推送和广播功能 |
| 1.1.0 | 2025-06-11 | 新增库位状态实时推送和广播功能,支持多种过滤条件和状态变化通知 |
| 1.1.0 | 2025-06-11 | 新增库位状态实时推送和广播功能,支持多种过滤条件和状态变化通知 |
| 1.2.0 | 2025-07-18 | 更新心跳检测机制所有WebSocket接口现在支持JSON和字符串两种格式的心跳检测 |

View File

@ -95,14 +95,12 @@
{
"station_name": "STATION-A-001",
"area_name": "一般存储区B",
"max_layers": 2,
"max_layers": 1,
"layers": [
{
"layer_name": "1-1" // 库位名称
},
{
"layer_name": "1-2" // 库位名称
"layer_name": "DSA_2_1_1" // 库位名称
}
]
},
{
@ -111,32 +109,28 @@
"max_layers": 1,
"layers": [
{
"layer_name": "2-1" //库位名称
"layer_name": "DSA_2_1_2" //库位名称
}
]
},
{
"station_name": "STATION-B-004",
"area_name": "一般存储区C",
"max_layers": 3,
"area_name": "一般存储区B",
"max_layers": 1,
"layers": [
{
"layer_name": "4-1" //库位名称
},
{
"layer_name": "4-2" // 库位名称
},
{
"layer_name": "4-3"// 库位名称
"layer_name": "DSA_2_1_3" //库位名称
}
]
},
{
"station_name": "STATION-B-003",
"area_name": "一般存储区B",
"max_layers": 1,
"layers": [
{
"layer_name": "3-1" //库位名称
"layer_name": "DSA_2_1_4" //库位名称
}
]
}

View File

@ -0,0 +1,242 @@
# 外部任务接口文档
## 外部任务模块接口
### 1. 创建新任务 (create_new_task)
#### 接口描述
根据任务类型自动选择对应的任务模板并执行任务,用于外部系统调用创建和启动任务。
#### 请求方式
- **HTTP方法**: POST
- **接口路径**: `/newTask`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|-------|------|-----|------|
| ReqCode | String | 是 | 请求唯一标识码 |
| SourceID | String | 否 | 来源ID |
| TargetID | String | 是 | 目标ID |
| TaskType | TaskTypeEnum | 是 | 任务类型 |
#### 任务类型枚举 (TaskTypeEnum)
| 枚举值 | 描述 | 模板ID |
|-------|------|-------|
| GG2MP | 高柜到MP | template_gg2mp_id |
| GGFK2MP | 高柜发库到MP | template_ggfk2mp_id |
| GT2MP | 高台到MP | 571985c1-cfa5-4186-8acd-6e3868a5e08c |
| GTFK2MP | 高台发库到MP | template_gtfk2mp_id |
| ZG2MP | 中柜到MP | template_zg2mp_id |
| QZ2MP | 清洗到MP | template_qz2mp_id |
| LG2MP | 料柜到MP | template_lg2mp_id |
| PHZ2MP | 配货站到MP | template_phz2mp_id |
| MP2GG | MP到高柜 | template_mp2gg_id |
| MP2GGFK | MP到高柜发库 | template_mp2ggfk_id |
| MP2GT | MP到高台 | template_mp2gt_id |
| MP2GTFK | MP到高台发库 | template_mp2gtfk_id |
| MP2ZG | MP到中柜 | template_mp2zg_id |
| MP2QZ | MP到清洗 | template_mp2qz_id |
| MP2LG | MP到料柜 | template_mp2lg_id |
| MP2PHZ | MP到配货站 | template_mp2phz_id |
#### 请求示例
```json
{
"ReqCode": "123e4567-e89b-12d3-a456-426614174000",
"SourceID": "WH-A-001",
"TargetID": "WH-B-002",
"TaskType": "GT2MP"
}
```
#### 响应参数
```json
{
"code": 0,
"reqCode": "123e4567-e89b-12d3-a456-426614174000",
"message": "成功",
"rowCount": 1
}
```
#### 错误响应
1. 不支持的任务类型时:
```json
{
"code": 400,
"reqCode": "123e4567-e89b-12d3-a456-426614174000",
"message": "不支持的任务类型: UNKNOWN_TYPE",
"rowCount": 0
}
```
2. 任务启动失败时:
```json
{
"code": 500,
"reqCode": "123e4567-e89b-12d3-a456-426614174000",
"message": "任务启动失败",
"rowCount": 0
}
```
3. 系统异常时:
```json
{
"code": 500,
"reqCode": "123e4567-e89b-12d3-a456-426614174000",
"message": "创建任务失败: [详细错误信息]",
"rowCount": 0
}
```
### 2. AGV调度任务 (GenAgvSchedulingTask)
#### 接口描述
用于生成AGV调度任务支持更丰富的调度参数配置包括位置路径、安全密钥验证等。
#### 请求方式
- **HTTP方法**: POST
- **接口路径**: `/GenAgvSchedulingTask`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|-------|------|-----|------|
| ReqCode | String | 是 | 请求唯一标识码 |
| TaskTyp | String | 是 | 任务类型使用TaskTypeEnum中的值 |
| SecurityKey | String | 是 | 安全密钥 |
| Type | String | 是 | 类型标识 |
| TaskCode | String | 是 | 任务代码 |
| SubType | String | 是 | 子类型标识 |
| AreaPositonCode | String | 是 | 区域位置代码 |
| AreaPositonName | String | 是 | 区域位置名称 |
| PositionCodePath | Array<PositionCodePath> | 是 | 位置代码路径 |
| ClientCode | String | 否 | 客户端代码 |
| TokenCode | String | 否 | 令牌代码 |
#### PositionCodePath 参数结构
| 参数名 | 类型 | 必填 | 描述 |
|-------|------|-----|------|
| PositionCode | String | 是 | 位置代码 |
| Type | String | 是 | 类型 |
#### 请求示例
```json
{
"ReqCode": "a98334b940af48328290389a64b71bcc",
"TaskTyp": "MP2GGFK",
"SecurityKey": "2JN4YW5IJSQRXDEPBT3YQKEGJMT2GS1X",
"Type": "1",
"TaskCode": "d074b178e9c54a72a484a782d8955aa3",
"SubType": "3",
"AreaPositonCode": "1-1",
"AreaPositonName": "1-1,ZK",
"PositionCodePath": [
{
"PositionCode": "AP190",
"Type": "00"
},
{
"PositionCode": "AP313",
"Type": "04"
}
],
"ClientCode": "",
"TokenCode": ""
}
```
#### 响应参数
```json
{
"code": 0,
"reqCode": "a98334b940af48328290389a64b71bcc",
"message": "成功",
"rowCount": 0
}
```
#### 错误响应
1. 安全密钥为空时:
```json
{
"code": 400,
"reqCode": "a98334b940af48328290389a64b71bcc",
"message": "安全密钥不能为空",
"rowCount": 0
}
```
2. 不支持的任务类型时:
```json
{
"code": 400,
"reqCode": "a98334b940af48328290389a64b71bcc",
"message": "不支持的任务类型: UNKNOWN_TYPE",
"rowCount": 0
}
```
3. 任务启动失败时:
```json
{
"code": 500,
"reqCode": "a98334b940af48328290389a64b71bcc",
"message": "任务启动失败",
"rowCount": 0
}
```
4. 系统异常时:
```json
{
"code": 500,
"reqCode": "a98334b940af48328290389a64b71bcc",
"message": "创建任务失败: [详细错误信息]",
"rowCount": 0
}
```
## 通用说明
### 响应码说明
| 响应码 | 描述 |
|-------|------|
| 0 | 成功 |
| 400 | 请求参数错误 |
| 404 | 资源不存在 |
| 409 | 冲突(如任务已在运行) |
| 500 | 服务器内部错误 |
### 安全说明
1. 所有接口都需要提供有效的请求标识码(ReqCode)
2. AGV调度任务接口需要提供有效的安全密钥(SecurityKey)
3. 系统会记录所有请求的客户端信息和来源
4. 建议在生产环境中启用HTTPS
### 调用注意事项
1. 请求标识码(ReqCode)应保证全局唯一性
2. 任务类型必须在支持的枚举范围内
3. 系统会根据任务类型自动选择对应的任务模板
4. 位置代码路径用于指定AGV的行驶路径
5. 系统支持并发调用,但相同设备的相同任务可能有限制
### 监控和日志
1. 所有接口调用都会记录详细日志
2. 支持通过ReqCode追踪任务执行状态
3. 错误信息会包含足够的上下文用于排查问题
4. 建议定期监控接口响应时间和成功率

View File

@ -22,7 +22,13 @@
### 1. 获取库位列表 (GET /api/vwed-operate-point/list)
获取库位列表,支持多种筛选条件和分页查询。
获取库位列表,支持多种筛选条件、排序功能和分页查询。
**排序功能说明:**
- `layer_name_sort`:支持按层名称排序,如 GSD_1_1_1 格式,先按数字部分排序,然后按字母部分排序
- `station_name_sort`:支持按站点名称排序,如 AP1 格式,先按数字部分排序,然后按字母部分排序
- 排序规则:首先按数字排序,如果数字都一样的话,按照字母排序
- 可以同时使用两种排序,优先按 layer_name_sort 排序,然后按 station_name_sort 排序
#### 请求参数
@ -38,6 +44,8 @@
| is_empty_tray | boolean | 否 | 是否空托盘 |
| include_operate_point_info | boolean | 否 | 是否包含动作点信息默认true |
| include_extended_fields | boolean | 否 | 是否包含扩展字段默认true |
| layer_name_sort | boolean | 否 | 层名称排序true升序默认、false降序 |
| station_name_sort | boolean | 否 | 站点名称排序true升序默认、false降序 |
| page | integer | 否 | 页码默认1 |
| page_size | integer | 否 | 每页数量默认20最大100 |
@ -152,10 +160,23 @@
#### 调用示例
**基本查询:**
```bash
GET /api/vwed-operate-point/list?scene_id=scene-001&is_occupied=false&page=1&page_size=20
```
**带排序的查询:**
```bash
# 按层名称升序排序
GET /api/vwed-operate-point/list?layer_name_sort=true&page=1&page_size=20
# 按站点名称降序排序
GET /api/vwed-operate-point/list?station_name_sort=false&page=1&page_size=20
# 组合排序:先按层名称升序,再按站点名称降序
GET /api/vwed-operate-point/list?layer_name_sort=true&station_name_sort=false&page=1&page_size=20
```
---
### 2. 更新库位状态 (PUT /api/vwed-operate-point/status)

Binary file not shown.

Binary file not shown.

4
app.py
View File

@ -63,8 +63,8 @@ if __name__ == "__main__":
# 从环境变量中获取端口默认为8000
import time
# start_time = time.time()
port = int(os.environ.get("PORT", settings.SERVER_PORT))
# port = int(os.environ.get("PORT", settings.SERVER_PORT))
port = 8001
# 打印启动配置信息
logger.info(f"服务器配置 - Host: 0.0.0.0, Port: {port}, Workers: {settings.SERVER_WORKERS}, Reload: {settings.SERVER_RELOAD}")
end_time = time.time()

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -53,25 +53,6 @@
}
]
},
{
"name": "preBinTask",
"type": "String",
"label": "预置binTask",
"description": "",
"required": false,
"defaultValue": null,
"options": [
{
"name": "param",
"type": "String",
"label": "参数",
"description": "",
"required": false,
"defaultValue": null,
"options": []
}
]
},
{
"name": "pick",
"type": "String",
@ -100,25 +81,6 @@
}
]
},
{
"name": "JackHeight",
"type": "String",
"label": "设置顶升高度 JackHeight",
"description": "",
"required": false,
"defaultValue": null,
"options": [
{
"name": "targetHeight",
"type": "Double",
"label": "目标高度",
"description": "",
"required": true,
"defaultValue": null,
"options": []
}
]
},
{
"name": "drop",
"type": "String",
@ -156,238 +118,6 @@
"options": []
}
]
},
{
"name": "ForkLoad",
"type": "String",
"label": "叉车取货 ForkLoad",
"description": "",
"required": false,
"defaultValue": null,
"options": [
{
"name": "start_height",
"type": "Double",
"label": "起始高度",
"description": "",
"required": false,
"defaultValue": null,
"options": []
},
{
"name": "end_height",
"type": "Double",
"label": "结束高度",
"description": "",
"required": false,
"defaultValue": null,
"options": []
},
{
"name": "inspired_unique",
"type": "Boolean",
"label": "启用识别",
"description": "",
"required": false,
"defaultValue": null,
"options": []
},
{
"name": "unique_file",
"type": "String",
"label": "识别文件",
"description": "",
"required": false,
"defaultValue": null,
"options": []
},
{
"name": "special_height",
"type": "Double",
"label": "识别后高度",
"description": "",
"required": false,
"defaultValue": null,
"options": []
}
]
},
{
"name": "ForkUnload",
"type": "String",
"label": "降低叉齿 ForkUnload",
"description": "",
"required": false,
"defaultValue": null,
"options": [
{
"name": "start_height",
"type": "Double",
"label": "起始高度",
"description": "",
"required": false,
"defaultValue": null,
"options": []
},
{
"name": "end_height",
"type": "Double",
"label": "结束高度",
"description": "",
"required": false,
"defaultValue": null,
"options": []
},
{
"name": "inspired_unique",
"type": "Boolean",
"label": "启用识别",
"description": "",
"required": false,
"defaultValue": null,
"options": []
},
{
"name": "unique_file",
"type": "String",
"label": "识别文件",
"description": "",
"required": false,
"defaultValue": null,
"options": []
},
{
"name": "special_height",
"type": "Double",
"label": "识别后高度",
"description": "",
"required": false,
"defaultValue": null,
"options": []
}
]
},
{
"name": "ForkHeight",
"type": "String",
"label": "边走边升降叉齿 ForkHeight",
"description": "",
"required": false,
"defaultValue": null,
"options": [
{
"name": "start_height",
"type": "Double",
"label": "起始高度",
"description": "",
"required": false,
"defaultValue": null,
"options": []
},
{
"name": "fork_height",
"type": "Double",
"label": "货叉行走过程中举升高度",
"description": "",
"required": false,
"defaultValue": null,
"options": []
},
{
"name": "end_height",
"type": "Double",
"label": "结束高度",
"description": "",
"required": false,
"defaultValue": null,
"options": []
}
]
},
{
"name": "CustomCommand",
"type": "String",
"label": "自定义指令",
"description": "",
"required": false,
"defaultValue": null,
"options": [
{
"name": "command",
"type": "String",
"label": "指令方式",
"description": "",
"required": false,
"defaultValue": null,
"options": [
{
"name": "operation_name",
"type": "String",
"label": "操作名",
"description": "",
"required": true,
"defaultValue": null,
"options": []
},
{
"name": "param",
"type": "JSONArray",
"label": "属性",
"description": "",
"required": false,
"defaultValue": null,
"options": []
}
]
}
]
},
{
"name": "syspy/setDO.py",
"type": "String",
"label": "syspy/setDO.py",
"description": "",
"required": false,
"defaultValue": null,
"options": [
{
"name": "DO",
"type": "JSONArray",
"label": "设置DO",
"description": "",
"required": true,
"defaultValue": null,
"options": []
}
]
},
{
"name": "syspy/waitDO.py",
"type": "String",
"label": "syspy/waitDO.py",
"description": "",
"required": false,
"defaultValue": null,
"options": [
{
"name": "DO",
"type": "JSONArray",
"label": "设置DO",
"description": "",
"required": true,
"defaultValue": null,
"options": []
},
{
"name": "timeout",
"type": "Integer",
"label": "超时时间(秒)",
"description": "",
"required": false,
"defaultValue": null,
"options": []
}
]
}
]
}

View File

@ -239,7 +239,7 @@
"label": "是否为降序",
"description": "",
"required": false,
"defaultValue": false,
"defaultValue": null,
"options": []
}
],
@ -399,7 +399,7 @@
"extraInputParamsFunc": "",
"outputParams": {},
"contextVariables": {
"site": {
"siteId": {
"type": "Object",
"label": "选出的库位",
"description": null,
@ -564,7 +564,7 @@
"label": "加锁者",
"description": "",
"required": false,
"defaultValue": true,
"defaultValue": "",
"options": []
},
{
@ -645,7 +645,7 @@
"label": "解锁者",
"description": "",
"required": false,
"defaultValue": true,
"defaultValue": "",
"options": []
}
],

View File

@ -27,8 +27,9 @@ class DBConfig:
"pool_size": settings.DB_POOL_SIZE,
"max_overflow": settings.DB_MAX_OVERFLOW,
"pool_recycle": settings.DB_POOL_RECYCLE,
"pool_timeout": settings.DB_POOL_TIMEOUT,
"echo": False, # 强制关闭SQL日志输出
"pool_pre_ping": True
"pool_pre_ping": settings.DB_POOL_PRE_PING
}
@classmethod

View File

@ -245,9 +245,11 @@ class BaseConfig(BaseSettings):
DB_NAME: str = Field(default=_db_config['database'], env="DB_NAME")
DB_CHARSET: str = Field(default=_db_config['charset'], env="DB_CHARSET")
DB_ECHO: bool = False # 是否输出SQL语句
DB_POOL_SIZE: int = 10
DB_MAX_OVERFLOW: int = 20
DB_POOL_RECYCLE: int = 3600 # 连接池回收时间,防止连接过期
DB_POOL_SIZE: int = 50 # 增加连接池基础大小
DB_MAX_OVERFLOW: int = 100 # 增加溢出连接数
DB_POOL_RECYCLE: int = 1800 # 减少连接回收时间,防止连接过期
DB_POOL_TIMEOUT: int = 60 # 获取连接的超时时间
DB_POOL_PRE_PING: bool = True # 连接前检测连接可用性
# Redis配置
_redis_config = get_config_for_env(_env, 'redis')
@ -326,8 +328,8 @@ class BaseConfig(BaseSettings):
TASK_EXPORT_IV: str = Field(default="vwed1234task5678", env="TASK_EXPORT_IV") # 初始化向量
# 增强版任务调度器配置
TASK_SCHEDULER_MIN_WORKER_COUNT: int = 15 # 最小工作线程数
TASK_SCHEDULER_MAX_WORKER_COUNT: int = 30 # 最大工作线程数
TASK_SCHEDULER_MIN_WORKER_COUNT: int = 100 # 最小工作线程数
TASK_SCHEDULER_MAX_WORKER_COUNT: int = 150 # 最大工作线程数
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] # 工作线程分配比例
@ -341,13 +343,10 @@ class BaseConfig(BaseSettings):
TASK_SCHEDULER_CPU_THRESHOLD: float = 80.0 # CPU使用率阈值百分比
TASK_SCHEDULER_MEMORY_THRESHOLD: float = 80.0 # 内存使用率阈值(百分比)
TASK_SCHEDULER_AUTO_SCALE_INTERVAL: int = 120 # 自动扩缩容间隔(秒)
TASK_SCHEDULER_WORKER_HEARTBEAT_INTERVAL: int = 120 # 心跳间隔(秒)
TASK_SCHEDULER_WORKER_HEARTBEAT_INTERVAL: int = 1200 # 心跳间隔(秒)
# 告警同步配置
ALERT_SYNC_ENABLED: bool = Field(default=True, env="ALERT_SYNC_ENABLED") # 是否启用告警同步
ALERT_SYNC_HOST: str = Field(default="192.168.189.80", env="ALERT_SYNC_HOST") # 主系统IP
ALERT_SYNC_PORT: int = Field(default=8080, env="ALERT_SYNC_PORT") # 主系统端口
ALERT_SYNC_API_PATH: str = Field(default="/jeecg-boot/warning", env="ALERT_SYNC_API_PATH") # 告警API路径
ALERT_SYNC_TIMEOUT: int = Field(default=10, env="ALERT_SYNC_TIMEOUT") # 请求超时时间(秒)
ALERT_SYNC_RETRY_COUNT: int = Field(default=3, env="ALERT_SYNC_RETRY_COUNT") # 重试次数
ALERT_SYNC_RETRY_DELAY: int = Field(default=1, env="ALERT_SYNC_RETRY_DELAY") # 重试延迟(秒)
@ -365,6 +364,14 @@ class BaseConfig(BaseSettings):
MAP_GENERAL_STORAGE_CAPACITY_PER_POINT: int = Field(default=15, env="MAP_GENERAL_STORAGE_CAPACITY_PER_POINT") # 一般库区每个动作点增加的容量
MAP_GENERAL_STORAGE_LAYER_MULTIPLIER: float = Field(default=1.2, env="MAP_GENERAL_STORAGE_LAYER_MULTIPLIER") # 一般库区分层倍数
# 库位获取队列配置
STORAGE_QUEUE_MAX_WORKERS: int = Field(default=10, env="STORAGE_QUEUE_MAX_WORKERS") # 队列工作者数量
STORAGE_QUEUE_MAX_SIZE: int = Field(default=2000, env="STORAGE_QUEUE_MAX_SIZE") # 队列最大数量
STORAGE_QUEUE_ENABLE_TIMEOUT: bool = Field(default=False, env="STORAGE_QUEUE_ENABLE_TIMEOUT") # 是否启用超时
STORAGE_QUEUE_DEFAULT_TIMEOUT: int = Field(default=3600, env="STORAGE_QUEUE_DEFAULT_TIMEOUT") # 默认超时时间(秒),仅在启用超时时生效
STORAGE_QUEUE_CLEANUP_INTERVAL: int = Field(default=300, env="STORAGE_QUEUE_CLEANUP_INTERVAL") # 清理已完成请求的间隔(秒)
STORAGE_QUEUE_COMPLETED_REQUEST_TTL: int = Field(default=3600, env="STORAGE_QUEUE_COMPLETED_REQUEST_TTL") # 已完成请求保留时间(秒)
@property
def DATABASE_URL(self) -> str:
"""构建数据库连接URL"""
@ -396,10 +403,10 @@ class BaseConfig(BaseSettings):
return f"redis://:{encoded_password}@{self.REDIS_HOST}:{self.REDIS_PORT}/{self.REDIS_DB}"
return f"redis://{self.REDIS_HOST}:{self.REDIS_PORT}/{self.REDIS_DB}"
@property
def ALERT_SYNC_URL(self) -> str:
"""构建告警同步URL"""
return f"http://{self.ALERT_SYNC_HOST}:{self.ALERT_SYNC_PORT}{self.ALERT_SYNC_API_PATH}"
# @property
# def ALERT_SYNC_URL(self) -> str:
# """构建告警同步URL"""
# return f"http://{self.ALERT_SYNC_HOST}:{self.ALERT_SYNC_PORT}{self.ALERT_SYNC_API_PATH}"
# 更新为Pydantic v2的配置方式
model_config = {
@ -423,6 +430,11 @@ class DevelopmentConfig(BaseConfig):
MAP_DENSE_STORAGE_CAPACITY_PER_POINT: int = 5
MAP_GENERAL_STORAGE_BASE_CAPACITY: int = 15
MAP_GENERAL_STORAGE_CAPACITY_PER_POINT: int = 8
# 开发环境队列配置
# STORAGE_QUEUE_MAX_WORKERS: int = 5
# STORAGE_QUEUE_MAX_SIZE: int = 500
# STORAGE_QUEUE_ENABLE_TIMEOUT: bool = False # 开发环境也不启用超时
# 根据环境变量选择配置

View File

@ -20,7 +20,8 @@ tf_api_endpoints = {
"set_task_in_progress": "/task/vwedtask/{id}/inprogress",
"set_task_completed": "/task/vwedtask/{id}/completed",
"set_task_terminated": "/task/vwedtask/{id}/terminated",
"set_task_failed": "/task/vwedtask/{id}/failed"
"set_task_failed": "/task/vwedtask/{id}/failed",
"set_task_description":"/task/vwedtask/{id}/description"
}
# 系统内部服务API HTTP方法配置
@ -34,18 +35,42 @@ tf_api_methods = {
"set_task_in_progress": "PUT",
"set_task_completed": "PUT",
"set_task_terminated": "PUT",
"set_task_failed": "PUT"
"set_task_failed": "PUT",
"set_task_description": "PUT"
}
# 外部任务类型对应的优先级
TASK_TYPE_PRIORITY={
"GT": "6", # 缸体
"GG": "5", # 缸盖
"QZ": "4", # 曲轴
"ZG": "3",#
"PHZ": "2",
"LG": "1",
"OR": "1"
}
# 外部任务对应所属库区
TASK_TYPE_AREA={
"GT": "ZK/ZKG",
"GG": "ZK/ZKG",
"QZ": "KW",
"ZG": "ZK/ZKG",
"PHZ": "AGW/PL",
"LG": "AGW/PL"
}
# 从环境变量读取配置,或使用默认值
# TF_API_BASE_URL = os.getenv("TF_API_BASE_URL", "http://192.168.189.80:8080/jeecg-boot")
TF_API_BASE_URL = os.getenv("TF_API_BASE_URL", "http://111.231.146.230:4080/jeecg-boot")
TF_API_TIMEOUT = int(os.getenv("TF_API_TIMEOUT", "60"))
TF_API_TIMEOUT = int(os.getenv("TF_API_TIMEOUT", "10")) # 减少超时时间从60秒到10秒
TF_API_RETRY_TIMES = int(os.getenv("TF_API_RETRY_TIMES", "3"))
TF_API_MOCK_MODE = False
TF_API_TOKEN_HEADER = os.getenv("TF_API_TOKEN_HEADER", "X-Access-Token") # token请求头名称
TF_API_TOKEN = os.getenv("TF_API_TOKEN", "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NDk3NzY1MzEsInVzZXJuYW1lIjoiYWRtaW4ifQ.uRLHZuRQTrR2fHyA-dMzP46yXAa5wdjfdUcmr9PNY4g")
TF_WEB_POST = False # 是否过账
sync_disabled_label = "sync_disabled" # 是否限制接口调用的变量名
# 外部接口叫料模板id
CM_ID = "571985c1-cfa5-4186-8acd-6e3868a5e08c"
# 送货模板id
DG_ID = "e22cacb4-a580-45ba-949e-356f57fa1a43"
def get_tf_api_config() -> Dict[str, Any]:
"""获取天风系统API配置"""
return {

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -23,6 +23,7 @@ from data.models.operate_point import OperatePoint
from data.models.operate_point_layer import OperatePointLayer
from data.models.extended_property import ExtendedProperty, ExtendedPropertyTypeEnum
from data.models.storage_location_log import StorageLocationLog
from data.models.external_task_record import VWEDExternalTaskRecord, ExternalTaskTypeEnum, ExternalTaskStatusEnum
# 导出所有模型供应用程序使用
__all__ = [
@ -31,5 +32,6 @@ __all__ = [
'VWEDScript', 'VWEDScriptVersion', 'VWEDScriptLog', 'VWEDModbusConfig',
'VWEDCallDevice', 'VWEDCallDeviceButton', 'InterfaceDefHistory',
'StorageArea', 'StorageAreaType', 'OperatePoint', 'OperatePointLayer',
'ExtendedProperty', 'ExtendedPropertyTypeEnum', 'StorageLocationLog'
'ExtendedProperty', 'ExtendedPropertyTypeEnum', 'StorageLocationLog',
'VWEDExternalTaskRecord', 'ExternalTaskTypeEnum', 'ExternalTaskStatusEnum'
]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,137 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
外部任务执行记录模型
用于记录外部接口调用的任务执行情况
"""
import datetime
from sqlalchemy import Column, String, Integer, Text, Boolean, Index, Enum as SQLEnum
from sqlalchemy.dialects.mysql import DATETIME, LONGTEXT
from data.models.base import BaseModel
import enum
class ExternalTaskTypeEnum(enum.Enum):
"""外部任务类型枚举"""
NEW_TASK = "newTask" # /newTask 接口
GEN_AGV_SCHEDULING_TASK = "GenAgvSchedulingTask" # /GenAgvSchedulingTask 接口
class ExternalTaskStatusEnum(enum.Enum):
"""外部任务状态枚举"""
PENDING = "pending" # 待执行
RUNNING = "running" # 执行中
SUCCESS = "success" # 执行成功
FAILED = "failed" # 执行失败
CANCELLED = "cancelled" # 已取消
class VWEDExternalTaskRecord(BaseModel):
"""
外部任务执行记录模型
对应vwed_external_task_record表
功能记录外部接口调用的任务执行情况和关联关系
"""
__tablename__ = 'vwed_external_task_record'
__table_args__ = (
Index('idx_external_task_req_code', 'req_code'),
Index('idx_external_task_task_code', 'task_code'),
Index('idx_external_task_type', 'task_type'),
Index('idx_external_task_status', 'task_status'),
Index('idx_external_task_record_id', 'task_record_id'),
Index('idx_external_task_related_req_code', 'related_req_code'),
Index('idx_external_task_created_at', 'created_at'),
{
'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='主键ID')
req_code = Column(String(255), nullable=False, comment='请求标识码ReqCode')
task_type = Column(SQLEnum(ExternalTaskTypeEnum), nullable=False, comment='外部任务类型newTask或GenAgvSchedulingTask')
task_status = Column(SQLEnum(ExternalTaskStatusEnum), nullable=False, default=ExternalTaskStatusEnum.PENDING, comment='任务状态')
# 关联字段 - 用于两个任务之间的关联
task_code = Column(String(255), comment='任务代码TaskCodeGenAgvSchedulingTask专用')
related_req_code = Column(String(255), comment='关联的ReqCode用于关联newTask和GenAgvSchedulingTask')
# TaskEditService.run_task 返回的任务记录ID
task_record_id = Column(String(255), comment='内部任务记录ID用于检测任务是否结束')
# newTask 接口专用字段
source_id = Column(String(255), comment='来源IDSourceID')
target_id = Column(String(255), comment='目标IDTargetID')
business_task_type = Column(String(50), comment='业务任务类型TaskType如GT2MP')
# GenAgvSchedulingTask 接口专用字段
security_key = Column(String(255), comment='安全密钥SecurityKey')
type_field = Column(String(50), comment='类型字段Type')
sub_type = Column(String(50), comment='子类型SubType')
area_position_code = Column(String(255), comment='区域位置代码AreaPositonCode')
area_position_name = Column(String(255), comment='区域位置名称AreaPositonName')
position_code_path = Column(LONGTEXT, comment='位置代码路径JSONPositionCodePath')
client_code = Column(String(255), comment='客户端代码ClientCode')
token_code = Column(String(255), comment='令牌代码TokenCode')
# 请求和响应信息
request_params = Column(LONGTEXT, comment='完整请求参数JSON')
response_data = Column(LONGTEXT, comment='完整响应数据JSON')
response_code = Column(Integer, comment='响应状态码')
response_message = Column(Text, comment='响应消息')
response_row_count = Column(Integer, comment='响应行数rowCount')
# 执行时间信息
start_time = Column(DATETIME(fsp=6), comment='任务开始时间')
end_time = Column(DATETIME(fsp=6), comment='任务结束时间')
duration = Column(Integer, comment='执行时长(毫秒)')
# 客户端信息
client_ip = Column(String(50), comment='客户端IP地址')
client_info = Column(Text, comment='客户端信息JSON')
# 任务模板信息
template_id = Column(String(255), comment='使用的任务模板ID')
# 错误信息
error_message = Column(Text, comment='错误信息')
error_stack = Column(LONGTEXT, comment='错误堆栈信息')
# 备注信息
remarks = Column(Text, comment='备注信息')
def __repr__(self):
return f"<VWEDExternalTaskRecord(id='{self.id}', req_code='{self.req_code}', task_type='{self.task_type}', status='{self.task_status}')>"
def is_completed(self):
"""判断任务是否已完成(成功或失败)"""
return self.task_status in [ExternalTaskStatusEnum.SUCCESS, ExternalTaskStatusEnum.FAILED, ExternalTaskStatusEnum.CANCELLED]
def update_status(self, status, error_message=None, response_data=None):
"""更新任务状态"""
self.task_status = status
if error_message:
self.error_message = error_message
if response_data:
self.response_data = response_data
# 更新时间信息
now = datetime.datetime.now()
if status == ExternalTaskStatusEnum.RUNNING and not self.start_time:
self.start_time = now
elif status in [ExternalTaskStatusEnum.SUCCESS, ExternalTaskStatusEnum.FAILED, ExternalTaskStatusEnum.CANCELLED]:
if not self.end_time:
self.end_time = now
if self.start_time and not self.duration:
self.duration = int((self.end_time - self.start_time).total_seconds() * 1000) # 毫秒
def get_related_task(self, session):
"""获取关联的任务记录"""
if not self.related_req_code:
return None
return session.query(VWEDExternalTaskRecord).filter(
VWEDExternalTaskRecord.req_code == self.related_req_code
).first()

View File

@ -100,6 +100,28 @@ async def get_async_session():
await session.close()
@asynccontextmanager
async def get_async_session_read_committed():
"""
获取使用READ COMMITTED隔离级别的异步数据库会话
用于需要读取最新已提交数据的场景解决并发数据同步问题
Yields:
AsyncSession: 异步数据库会话对象
"""
session = AsyncSessionLocal()
try:
# 设置事务隔离级别为READ COMMITTED
await session.execute(text("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED"))
yield session
await session.commit()
except Exception as e:
await session.rollback()
raise e
finally:
await session.close()
async def get_async_db():
"""
获取异步数据库会话生成器用于FastAPI依赖注入

68229
logs/app.log

File diff suppressed because it is too large Load Diff

74182
logs/app.log.1 Normal file

File diff suppressed because it is too large Load Diff

96521
logs/app.log.2 Normal file

File diff suppressed because it is too large Load Diff

56329
logs/app.log.2025-08-03 Normal file

File diff suppressed because it is too large Load Diff

1797
logs/app.log.2025-08-05 Normal file

File diff suppressed because it is too large Load Diff

14734
logs/app.log.2025-08-06 Normal file

File diff suppressed because it is too large Load Diff

106059
logs/app.log.2025-08-07 Normal file

File diff suppressed because it is too large Load Diff

194808
logs/app.log.2025-08-08 Normal file

File diff suppressed because it is too large Load Diff

21791
logs/app.log.2025-08-10 Normal file

File diff suppressed because it is too large Load Diff

55620
logs/app.log.2025-08-11 Normal file

File diff suppressed because it is too large Load Diff

2396
logs/app.log.2025-08-12 Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -19,6 +19,7 @@ from routes.modbus_config_api import router as modbus_config_router
from routes.websocket_api import router as websocket_router
from routes.map_data_api import router as map_data_router
from routes.operate_point_api import router as operate_point_router
from routes.external_task_api import router as external_task_router
# 路由列表,按照注册顺序排列
routers = [
@ -33,7 +34,8 @@ routers = [
modbus_config_router,
websocket_router,
map_data_router,
operate_point_router
operate_point_router,
external_task_router
]
def register_routers(app):

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

1108
routes/external_task_api.py Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,73 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
外部任务接口模型模块
包含外部任务创建相关的请求和响应数据模型
"""
from typing import Optional, List, Dict, Any
from enum import Enum
from pydantic import BaseModel, Field
# 任务类型枚举
class TaskTypeEnum(str, Enum):
"""任务类型枚举"""
GG2MP = "GG2MP" # 高柜到MP
GGFK2MP = "GGFK2MP" # 高柜发库到MP
GT2MP = "GT2MP" # 高台到MP
GTFK2MP = "GTFK2MP" # 高台发库到MP
ZG2MP = "ZG2MP" # 中柜到MP
QZ2MP = "QZ2MP" # 清洗到MP
LG2MP = "LG2MP" # 料柜到MP
PHZ2MP = "PHZ2MP" # 配货站到MP
MP2GG = "MP2GG" # MP到高柜
MP2GGFK = "MP2GGFK" # MP到高柜发库
MP2GT = "MP2GT" # MP到高台
MP2GTFK = "MP2GTFK" # MP到高台发库
MP2ZG = "MP2ZG" # MP到中柜
MP2QZ = "MP2QZ" # MP到清洗
MP2LG = "MP2LG" # MP到料柜
MP2PHZ = "MP2PHZ" # MP到配货站
# 外部任务创建请求模型
class ExternalTaskRequest(BaseModel):
"""外部任务创建请求模型"""
ReqCode: str = Field(..., description="请求唯一标识码")
SourceID: str = Field("", description="来源ID")
TargetID: str = Field(..., description="目标ID")
TaskType: TaskTypeEnum = Field(..., description="任务类型")
# 外部任务创建响应模型
class ExternalTaskResponse(BaseModel):
"""外部任务创建响应模型"""
code: int = Field(..., description="响应码0表示成功")
reqCode: str = Field(..., description="请求唯一标识码")
message: str = Field(..., description="响应消息")
rowCount: int = Field(0, description="影响行数")
# 位置路径项模型
class PositionCodePath1(BaseModel):
"""位置路径项模型"""
PositionCode: str = Field(..., description="位置代码")
Type: str = Field(..., description="类型")
# AGV调度任务请求模型
class GenAgvSchedulingTaskRequest(BaseModel):
"""AGV调度任务请求模型"""
ReqCode: str = Field("", description="请求唯一标识码")
TaskTyp: str = Field("", description="任务类型")
SecurityKey: str = Field("", description="安全密钥")
Type: str = Field("", description="类型")
TaskCode: str = Field("", description="任务代码")
SubType: str = Field("", description="子类型")
AreaPositonCode: str = Field("", description="区域位置代码")
AreaPositonName: str = Field("", description="区域位置名称")
PositionCodePath: List[PositionCodePath1] = Field(..., description="位置代码路径")
ClientCode: str = Field("", description="客户端代码")
TokenCode: str = Field("", description="令牌代码")
# 取消任务请求模型
class CancelTaskRequest(BaseModel):
"""取消任务请求模型"""
ReqCode: str = Field(..., description="请求唯一标识码")

View File

@ -118,6 +118,8 @@ class StorageLocationListRequest(BaseModel):
is_empty_tray: Optional[bool] = Field(None, description="是否空托盘")
include_operate_point_info: bool = Field(True, description="是否包含动作点信息")
include_extended_fields: bool = Field(True, description="是否包含扩展字段")
layer_name_sort: Optional[bool] = Field(None, description="层名称排序true升序默认、false降序")
station_name_sort: Optional[bool] = Field(None, description="站点名称排序true升序默认、false降序")
page: int = Field(1, ge=1, description="页码")
page_size: int = Field(20, ge=1, le=100, description="每页数量")

View File

@ -205,6 +205,7 @@ class TaskEditRunRequest(BaseModel):
modbus_configs: Optional[List[ModbusConfigParam]] = Field(None, description="任务关联的Modbus配置列表用于任务执行时的Modbus操作")
use_modbus: Optional[bool] = Field(False, description="是否使用Modbus通信")
modbus_timeout: Optional[int] = Field(5000, description="Modbus通信超时时间(毫秒)")
priority: Optional[int] = Field(1, description="任务优先级 大于等于 1")
# 重新定义更准确的任务参数模型,确保包含所有必要字段

View File

@ -78,13 +78,15 @@ async def get_storage_location_list(
scene_id: Optional[str] = Query(None, description="场景ID"),
storage_area_id: Optional[str] = Query(None, description="库区ID"),
station_name: Optional[str] = Query(None, description="站点名称(支持模糊搜索)"),
layer_name: Optional[str] = Query(None, description="名称(支持模糊搜索)"),
layer_name: Optional[str] = Query(None, description="库位名称(支持模糊搜索)"),
is_disabled: Optional[bool] = Query(None, description="是否禁用"),
is_occupied: Optional[bool] = Query(None, description="是否占用"),
is_locked: Optional[bool] = Query(None, description="是否锁定"),
is_empty_tray: Optional[bool] = Query(None, description="是否空托盘"),
include_operate_point_info: bool = Query(True, description="是否包含动作点信息"),
include_extended_fields: bool = Query(True, description="是否包含扩展字段"),
layer_name_sort: Optional[bool] = Query(None, description="层名称排序true升序默认、false降序"),
station_name_sort: Optional[bool] = Query(None, description="站点名称排序true升序默认、false降序"),
page: int = Query(1, ge=1, description="页码"),
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
db: Session = Depends(get_db)
@ -146,6 +148,8 @@ async def get_storage_location_list(
is_empty_tray=is_empty_tray,
include_operate_point_info=include_operate_point_info,
include_extended_fields=include_extended_fields,
layer_name_sort=layer_name_sort,
station_name_sort=station_name_sort,
page=page,
page_size=page_size
)

125
routes/storage_queue_api.py Normal file
View File

@ -0,0 +1,125 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
库位队列管理API
提供队列状态监控和管理接口
"""
from fastapi import APIRouter, HTTPException, Query
from typing import Dict, Any, Optional
from services.execution.handlers.storage_queue_manager import storage_queue_manager
from utils.api_response import success_response, error_response
from utils.logger import get_logger
logger = get_logger("routes.storage_queue_api")
router = APIRouter(prefix="/api/storage-queue", tags=["库位队列管理"])
@router.get("/stats", summary="获取队列统计信息")
async def get_queue_stats() -> Dict[str, Any]:
"""获取队列统计信息"""
try:
stats = storage_queue_manager.get_queue_stats()
return success_response(data=stats, message="获取队列统计信息成功")
except Exception as e:
logger.error(f"获取队列统计信息失败: {str(e)}")
return error_response(f"获取队列统计信息失败: {str(e)}")
@router.get("/status/{request_id}", summary="获取请求状态")
async def get_request_status(request_id: str) -> Dict[str, Any]:
"""获取特定请求的状态"""
try:
status = await storage_queue_manager.get_request_status(request_id)
if status:
return success_response(data=status, message="获取请求状态成功")
else:
return error_response("请求不存在")
except Exception as e:
logger.error(f"获取请求状态失败: {str(e)}")
return error_response(f"获取请求状态失败: {str(e)}")
@router.post("/cancel/{request_id}", summary="取消请求")
async def cancel_request(request_id: str) -> Dict[str, Any]:
"""取消指定的请求"""
try:
success = await storage_queue_manager.cancel_request(request_id)
if success:
return success_response(message=f"取消请求成功: {request_id}")
else:
return error_response("请求不存在或无法取消")
except Exception as e:
logger.error(f"取消请求失败: {str(e)}")
return error_response(f"取消请求失败: {str(e)}")
@router.get("/health", summary="队列健康检查")
async def queue_health_check() -> Dict[str, Any]:
"""检查队列管理器的健康状态"""
try:
stats = storage_queue_manager.get_queue_stats()
# 判断健康状态
health_status = "healthy"
issues = []
# 检查队列大小
if stats.get('queue_size', 0) > 800: # 队列接近满载
health_status = "warning"
issues.append("队列接近满载")
# 检查活跃工作者数量
if stats.get('active_workers', 0) == 0:
health_status = "critical"
issues.append("没有活跃的工作者")
# 检查失败率
total_requests = stats.get('requests_total', 0)
if total_requests > 0:
failure_rate = stats.get('requests_failed', 0) / total_requests
if failure_rate > 0.1: # 失败率超过10%
health_status = "warning"
issues.append(f"失败率过高: {failure_rate:.2%}")
return success_response(data={
"status": health_status,
"issues": issues,
"stats": stats
}, message="队列健康检查完成")
except Exception as e:
logger.error(f"队列健康检查失败: {str(e)}")
return error_response(f"队列健康检查失败: {str(e)}")
@router.post("/start", summary="启动队列管理器")
async def start_queue_manager() -> Dict[str, Any]:
"""启动队列管理器"""
try:
await storage_queue_manager.start()
return success_response(message="队列管理器启动成功")
except Exception as e:
logger.error(f"启动队列管理器失败: {str(e)}")
return error_response(f"启动队列管理器失败: {str(e)}")
@router.post("/stop", summary="停止队列管理器")
async def stop_queue_manager() -> Dict[str, Any]:
"""停止队列管理器"""
try:
await storage_queue_manager.stop()
return success_response(message="队列管理器停止成功")
except Exception as e:
logger.error(f"停止队列管理器失败: {str(e)}")
return error_response(f"停止队列管理器失败: {str(e)}")
@router.get("/config", summary="获取队列配置")
async def get_queue_config() -> Dict[str, Any]:
"""获取队列管理器配置"""
try:
config = {
"max_workers": storage_queue_manager.max_workers,
"max_queue_size": storage_queue_manager.max_queue_size,
"handlers_registered": list(storage_queue_manager.handlers.keys())
}
return success_response(data=config, message="获取队列配置成功")
except Exception as e:
logger.error(f"获取队列配置失败: {str(e)}")
return error_response(f"获取队列配置失败: {str(e)}")

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