新增上传队列显示
This commit is contained in:
@@ -0,0 +1,353 @@
|
||||
# 前端队列状态 API 接口文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档说明队列状态查询的 API 接口,前端可以通过这些接口获取队列统计信息和文档排队位置。
|
||||
|
||||
**重要更新 (2026-01-29)**:
|
||||
- `documents` 字段现在从**数据库**查询,而不是 Redis metrics
|
||||
- 即使 Worker 未启动,也能正确显示等待处理的文档数量
|
||||
- 新增 `waiting_ids` 和 `processing_details` 字段
|
||||
|
||||
---
|
||||
|
||||
## 接口 1:查询队列整体状态
|
||||
|
||||
**用途**:获取全局队列统计信息(排队数、处理数)
|
||||
|
||||
**请求**:
|
||||
```http
|
||||
GET /api/v2/system/queue/status
|
||||
Authorization: Bearer <your_jwt_token>
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"timestamp": "2026-01-28T16:45:00.123456",
|
||||
"queue": {
|
||||
"pending_tasks": 5,
|
||||
"processing_tasks": 2,
|
||||
"available_slots": 2,
|
||||
"max_concurrent": 4
|
||||
},
|
||||
"documents": {
|
||||
"waiting": 3,
|
||||
"waiting_ids": [101, 102, 103],
|
||||
"processing": 2,
|
||||
"processing_ids": [123, 456],
|
||||
"processing_details": [
|
||||
{"id": 123, "status": "Cutting"},
|
||||
{"id": 456, "status": "Extractioning"}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**响应字段说明**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `success` | boolean | 请求是否成功 |
|
||||
| `timestamp` | string | 查询时间 |
|
||||
| `queue.pending_tasks` | number | Celery 队列中等待的任务数 |
|
||||
| `queue.processing_tasks` | number | 正在处理的任务数(通过并发许可统计) |
|
||||
| `queue.available_slots` | number | 可用的处理槽位 |
|
||||
| `queue.max_concurrent` | number | 最大并发数 |
|
||||
| `documents.waiting` | number | **等待处理的文档数**(数据库 status='Queued') |
|
||||
| `documents.waiting_ids` | number[] | **等待中的文档ID列表** |
|
||||
| `documents.processing` | number | **正在处理的文档数**(数据库 status in ['Cutting', 'Extractioning', 'Evaluationing']) |
|
||||
| `documents.processing_ids` | number[] | 正在处理的文档ID列表 |
|
||||
| `documents.processing_details` | object[] | **处理中文档的详细状态** |
|
||||
|
||||
**数据来源说明**:
|
||||
|
||||
| 字段 | 数据来源 | 说明 |
|
||||
|------|----------|------|
|
||||
| `queue.*` | Redis | 从 Celery 队列和并发控制器获取 |
|
||||
| `documents.*` | PostgreSQL | 从数据库 documents 表查询 |
|
||||
|
||||
> ⚠️ **注意**:即使 Worker 未启动,`documents.waiting` 也会正确显示等待处理的文档数量。
|
||||
|
||||
---
|
||||
|
||||
## 接口 2:查询单个文档位置
|
||||
|
||||
**用途**:查询指定文档在队列中的位置、预估等待时间、以及细分处理状态
|
||||
|
||||
**请求**:
|
||||
```http
|
||||
GET /api/v2/system/queue/position/{document_id}
|
||||
Authorization: Bearer <your_jwt_token>
|
||||
```
|
||||
|
||||
**响应示例 - 排队中**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"document_id": 456,
|
||||
"status": "Queued",
|
||||
"position": 3,
|
||||
"ahead_count": 2,
|
||||
"total_in_queue": 5,
|
||||
"estimated_wait_minutes": 4,
|
||||
"message": "文档在队列中排第 3 位,前面有 2 个文档"
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例 - OCR 识别中**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"document_id": 123,
|
||||
"status": "Cutting",
|
||||
"position": 0,
|
||||
"ahead_count": 0,
|
||||
"message": "OCR 识别中"
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例 - AI 分析中**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"document_id": 123,
|
||||
"status": "Extractioning",
|
||||
"position": 0,
|
||||
"ahead_count": 0,
|
||||
"message": "AI 分析中"
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例 - 规则检查中**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"document_id": 123,
|
||||
"status": "Evaluationing",
|
||||
"position": 0,
|
||||
"ahead_count": 0,
|
||||
"message": "规则检查中"
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例 - 处理完成**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"document_id": 789,
|
||||
"status": "Processed",
|
||||
"position": null,
|
||||
"ahead_count": 0,
|
||||
"message": "文档处理完成"
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例 - 处理失败**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"document_id": 789,
|
||||
"status": "Failed",
|
||||
"position": null,
|
||||
"ahead_count": 0,
|
||||
"message": "文档处理失败"
|
||||
}
|
||||
```
|
||||
|
||||
**响应字段说明**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `success` | boolean | 请求是否成功 |
|
||||
| `document_id` | number | 文档ID |
|
||||
| `status` | string | **细分状态**(见下表) |
|
||||
| `position` | number | 在队列中的位置(从1开始,处理中或完成时为0或null) |
|
||||
| `ahead_count` | number | 前面排队的文档数 |
|
||||
| `total_in_queue` | number | 队列中的总文档数(仅排队时返回) |
|
||||
| `estimated_wait_minutes` | number | 预估等待时间(分钟,仅排队时返回) |
|
||||
| `message` | string | 状态描述 |
|
||||
|
||||
**status 字段可能的值**:
|
||||
|
||||
| 状态值 | 说明 | 进度 | 建议显示 |
|
||||
|--------|------|------|----------|
|
||||
| `Queued` | 排队中 | 0% | 显示排队位置和预估时间 |
|
||||
| `Cutting` | OCR 识别中 | 33% | 显示进度条 + "正在识别文档..." |
|
||||
| `Extractioning` | AI 分析中 | 66% | 显示进度条 + "正在分析内容..." |
|
||||
| `Evaluationing` | 规则检查中 | 90% | 显示进度条 + "正在检查规则..." |
|
||||
| `Processed` | 处理完成 | 100% | 显示完成提示,可跳转查看结果 |
|
||||
| `Failed` | 处理失败 | - | 显示错误提示,可查看详情 |
|
||||
|
||||
---
|
||||
|
||||
## 文档状态流转图
|
||||
|
||||
```
|
||||
上传文档
|
||||
│
|
||||
▼
|
||||
┌─────────┐
|
||||
│ Queued │ ← 文档初始状态(等待 Worker 处理)
|
||||
└────┬────┘
|
||||
│ Worker 开始处理
|
||||
▼
|
||||
┌─────────┐
|
||||
│ Cutting │ ← OCR 识别阶段
|
||||
└────┬────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────┐
|
||||
│Extractioning │ ← AI 分析阶段
|
||||
└──────┬───────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────┐
|
||||
│Evaluationing │ ← 规则检查阶段
|
||||
└──────┬───────┘
|
||||
│
|
||||
├────────────┐
|
||||
▼ ▼
|
||||
┌──────────┐ ┌────────┐
|
||||
│Processed │ │ Failed │
|
||||
└──────────┘ └────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 前端集成建议
|
||||
|
||||
### 1. 上传后立即显示队列状态
|
||||
|
||||
```javascript
|
||||
// 上传文档后
|
||||
const uploadResult = await uploadDocument(file);
|
||||
const documentId = uploadResult.result.id;
|
||||
|
||||
// 立即查询队列状态
|
||||
const position = await api.get(`/system/queue/position/${documentId}`);
|
||||
if (position.status === 'Queued') {
|
||||
showNotification(`文档已加入队列,前面有 ${position.ahead_count} 个文档`);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 轮询文档处理状态
|
||||
|
||||
```javascript
|
||||
const pollDocumentStatus = async (documentId) => {
|
||||
const poll = async () => {
|
||||
const result = await api.get(`/system/queue/position/${documentId}`);
|
||||
|
||||
switch (result.status) {
|
||||
case 'Queued':
|
||||
updateProgress(0, `排队中,前面还有 ${result.ahead_count} 个文档`);
|
||||
break;
|
||||
case 'Cutting':
|
||||
updateProgress(33, 'OCR 识别中...');
|
||||
break;
|
||||
case 'Extractioning':
|
||||
updateProgress(66, 'AI 分析中...');
|
||||
break;
|
||||
case 'Evaluationing':
|
||||
updateProgress(90, '规则检查中...');
|
||||
break;
|
||||
case 'Processed':
|
||||
updateProgress(100, '处理完成');
|
||||
return result; // 停止轮询
|
||||
case 'Failed':
|
||||
showError('处理失败');
|
||||
return result; // 停止轮询
|
||||
}
|
||||
|
||||
// 继续轮询
|
||||
setTimeout(poll, 5000);
|
||||
};
|
||||
|
||||
return poll();
|
||||
};
|
||||
```
|
||||
|
||||
### 3. 显示全局队列状态
|
||||
|
||||
```javascript
|
||||
// 在页面顶部或侧边栏显示队列状态
|
||||
const QueueStatusBadge = () => {
|
||||
const [status, setStatus] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchStatus = async () => {
|
||||
const data = await api.get('/system/queue/status');
|
||||
setStatus(data);
|
||||
};
|
||||
|
||||
fetchStatus();
|
||||
const interval = setInterval(fetchStatus, 10000); // 每 10 秒刷新
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
if (!status) return null;
|
||||
|
||||
return (
|
||||
<div className="queue-status">
|
||||
<span>等待: {status.documents.waiting}</span>
|
||||
<span>处理中: {status.documents.processing}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 轮询建议
|
||||
|
||||
| 场景 | 推荐间隔 | 说明 |
|
||||
|------|----------|------|
|
||||
| 队列整体状态 | 10 秒 | 用于全局状态显示 |
|
||||
| 单个文档位置 | 5 秒 | 用于进度跟踪 |
|
||||
| 文档排队中 | 5 秒 | 显示排队位置变化 |
|
||||
| 文档处理中 | 3 秒 | 更频繁更新进度 |
|
||||
|
||||
**停止轮询条件**:当 `status` 变为 `Processed` 或 `Failed` 时停止轮询
|
||||
|
||||
---
|
||||
|
||||
## 错误响应
|
||||
|
||||
所有接口在发生错误时返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "错误描述信息"
|
||||
}
|
||||
```
|
||||
|
||||
**HTTP 状态码**:
|
||||
- `200` - 成功
|
||||
- `401` - 未授权(Token 无效或过期)
|
||||
- `500` - 服务器内部错误
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 为什么 `queue.pending_tasks` 和 `documents.waiting` 数量不一致?
|
||||
|
||||
**A**:
|
||||
- `queue.pending_tasks` 统计的是 Celery 队列中的**任务数**
|
||||
- `documents.waiting` 统计的是数据库中 `status='Queued'` 的**文档数**
|
||||
- 一个文档可能对应多个任务,或者有些任务不是文档处理任务
|
||||
|
||||
### Q2: Worker 未启动时会显示什么?
|
||||
|
||||
**A**:
|
||||
- `queue.pending_tasks` 会显示队列中等待的任务数
|
||||
- `documents.waiting` 会正确显示等待处理的文档数(从数据库查询)
|
||||
- `queue.processing_tasks` 和 `documents.processing` 为 0
|
||||
|
||||
### Q3: 如何判断系统是否正常运行?
|
||||
|
||||
**A**:
|
||||
- 如果 `documents.waiting > 0` 但 `queue.processing_tasks == 0` 且持续较长时间,可能 Worker 未启动
|
||||
- 正常情况下,应该有文档在处理(`processing > 0`)或所有文档已处理完(`waiting == 0`)
|
||||
Reference in New Issue
Block a user