|
@@ -0,0 +1,429 @@
|
|
|
+# 重试发送任务功能实现文档
|
|
|
+
|
|
|
+## 功能概述
|
|
|
+
|
|
|
+实现了传输队列中的【重试发送任务】功能,允许用户选择失败的发送任务并重新提交到后端执行。
|
|
|
+
|
|
|
+## 一、涉及的参与者
|
|
|
+
|
|
|
+### 1. 页面层(Page Layer)
|
|
|
+- **OutputlistPage** (`src/pages/patient/OutputList.tsx`)
|
|
|
+ - 主容器页面,负责展示传输队列
|
|
|
+ - 管理表格、分页和操作面板
|
|
|
+ - 维护任务选中状态
|
|
|
+
|
|
|
+### 2. 组件层(Component Layer)
|
|
|
+- **OutputOperationPanel** (`src/pages/patient/components/OutputOperationPanel.tsx`)
|
|
|
+ - 操作面板容器组件
|
|
|
+
|
|
|
+- **OutputActionPanel** (`src/pages/patient/components/OutputActionPanel.tsx`)
|
|
|
+ - 包含重试和删除操作按钮
|
|
|
+ - 实现重试按钮的点击处理逻辑
|
|
|
+
|
|
|
+### 3. 状态管理层(State Management Layer)
|
|
|
+- **sendJobSlice** (`src/states/output/sendJob/slices/sendJobSlice.ts`)
|
|
|
+ - `retrySendJobThunk`: 重试任务的异步 Thunk
|
|
|
+ - `createRetryHandlers`: 处理重试结果的 handlers
|
|
|
+
|
|
|
+- **Redux Store**
|
|
|
+ - `sendJobSelection.selectedIds`: 维护选中的任务 IDs
|
|
|
+ - `sendJobEntities.data`: 存储任务数据
|
|
|
+
|
|
|
+### 4. API 层(API Layer)
|
|
|
+- **sendJobActions** (`src/API/output/sendJobActions.ts`)
|
|
|
+ - `retrySendTask(taskId)`: 调用后端重试接口
|
|
|
+ - `RetrySendTaskRequest`: 请求参数类型
|
|
|
+ - `RetrySendTaskResponse`: 响应数据类型
|
|
|
+
|
|
|
+### 5. 领域模型层(Domain Layer)
|
|
|
+- **SendJob** (`src/domain/output/sendJob.ts`)
|
|
|
+ - 任务实体模型
|
|
|
+ - 包含 `task_id`、`status`、`retry_count` 等字段
|
|
|
+
|
|
|
+## 二、实现修改清单
|
|
|
+
|
|
|
+### 2.1 API 层修改
|
|
|
+
|
|
|
+**文件:** `src/API/output/sendJobActions.ts`
|
|
|
+
|
|
|
+#### 修改内容:
|
|
|
+
|
|
|
+1. **修改请求参数接口**
|
|
|
+```typescript
|
|
|
+// 修改前
|
|
|
+export interface RetrySendTaskRequest {
|
|
|
+ instance_uid: string;
|
|
|
+}
|
|
|
+
|
|
|
+// 修改后
|
|
|
+export interface RetrySendTaskRequest {
|
|
|
+ task_id: string;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+2. **修改 retrySendTask 函数**
|
|
|
+```typescript
|
|
|
+// 修改前
|
|
|
+export const retrySendTask = async (
|
|
|
+ instanceUid: string
|
|
|
+): Promise<RetrySendTaskResponse> => {
|
|
|
+ const response = await axiosInstance.post<RetrySendTaskResponse>(
|
|
|
+ '/auth/scp/store_reply',
|
|
|
+ { instance_uid: instanceUid }
|
|
|
+ );
|
|
|
+ // ...
|
|
|
+}
|
|
|
+
|
|
|
+// 修改后
|
|
|
+export const retrySendTask = async (
|
|
|
+ taskId: string
|
|
|
+): Promise<RetrySendTaskResponse> => {
|
|
|
+ const response = await axiosInstance.post<RetrySendTaskResponse>(
|
|
|
+ '/auth/scp/store_reply',
|
|
|
+ { task_id: taskId }
|
|
|
+ );
|
|
|
+ // ...
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 2.2 状态管理层修改
|
|
|
+
|
|
|
+**文件:** `src/states/output/sendJob/slices/sendJobSlice.ts`
|
|
|
+
|
|
|
+#### 修改内容:
|
|
|
+
|
|
|
+1. **简化 retrySendJobThunk 参数**
|
|
|
+```typescript
|
|
|
+// 修改前
|
|
|
+export const retrySendJobThunk = createAsyncThunk(
|
|
|
+ 'sendJob/retry',
|
|
|
+ async ({ taskId, instanceUid }: { taskId: string; instanceUid: string }) => {
|
|
|
+ const result = await retrySendTask(instanceUid);
|
|
|
+ return { taskId, result };
|
|
|
+ }
|
|
|
+);
|
|
|
+
|
|
|
+// 修改后
|
|
|
+export const retrySendJobThunk = createAsyncThunk(
|
|
|
+ 'sendJob/retry',
|
|
|
+ async (taskId: string) => {
|
|
|
+ const result = await retrySendTask(taskId);
|
|
|
+ return { taskId, result };
|
|
|
+ }
|
|
|
+);
|
|
|
+```
|
|
|
+
|
|
|
+### 2.3 组件层修改
|
|
|
+
|
|
|
+**文件:** `src/pages/patient/components/OutputActionPanel.tsx`
|
|
|
+
|
|
|
+#### 修改内容:
|
|
|
+
|
|
|
+1. **添加必要的导入**
|
|
|
+```typescript
|
|
|
+import { message } from 'antd';
|
|
|
+import { useAppDispatch, useAppSelector } from '@/states/store';
|
|
|
+import { retrySendJobThunk } from '@/states/output/sendJob/slices/sendJobSlice';
|
|
|
+```
|
|
|
+
|
|
|
+2. **添加重试处理逻辑**
|
|
|
+```typescript
|
|
|
+const OutputActionPanel: React.FC = () => {
|
|
|
+ const dispatch = useAppDispatch();
|
|
|
+ const selectedIds = useAppSelector((state) => state.sendJobSelection.selectedIds);
|
|
|
+
|
|
|
+ // 处理重试按钮点击
|
|
|
+ const handleRetry = async () => {
|
|
|
+ // 校验是否有选中的任务
|
|
|
+ if (selectedIds.length === 0) {
|
|
|
+ message.warning('请先选择要重试的任务');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 遍历选中的任务,逐个调用重试 API
|
|
|
+ const retryPromises = selectedIds.map((taskId) =>
|
|
|
+ dispatch(retrySendJobThunk(taskId)).unwrap()
|
|
|
+ );
|
|
|
+
|
|
|
+ await Promise.all(retryPromises);
|
|
|
+ message.success(`成功提交 ${selectedIds.length} 个重试任务`);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('重试任务失败:', error);
|
|
|
+ message.error('重试任务失败,请稍后再试');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ // ...
|
|
|
+ );
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+3. **添加按钮点击事件**
|
|
|
+```typescript
|
|
|
+<Button
|
|
|
+ type="default"
|
|
|
+ icon={/* ... */}
|
|
|
+ aria-label="重试"
|
|
|
+ style={{ width: '1.5rem', height: '1.5rem' }}
|
|
|
+ onClick={handleRetry} // 添加此行
|
|
|
+/>
|
|
|
+```
|
|
|
+
|
|
|
+## 三、组件交互流程
|
|
|
+
|
|
|
+### 3.1 用户操作流程
|
|
|
+
|
|
|
+```mermaid
|
|
|
+sequenceDiagram
|
|
|
+ participant User as 用户
|
|
|
+ participant Button as 重试按钮
|
|
|
+ participant Handler as handleRetry
|
|
|
+ participant Redux as Redux Store
|
|
|
+ participant Thunk as retrySendJobThunk
|
|
|
+ participant API as retrySendTask API
|
|
|
+ participant Backend as 后端服务
|
|
|
+
|
|
|
+ User->>Button: 1. 点击重试按钮
|
|
|
+ Button->>Handler: 2. 触发 onClick
|
|
|
+ Handler->>Redux: 3. 获取 selectedIds
|
|
|
+
|
|
|
+ alt 没有选中任务
|
|
|
+ Handler->>User: 4a. 显示警告消息
|
|
|
+ else 有选中任务
|
|
|
+ loop 每个选中的任务
|
|
|
+ Handler->>Thunk: 4b. dispatch(retrySendJobThunk(taskId))
|
|
|
+ Thunk->>API: 5. 调用 retrySendTask(taskId)
|
|
|
+ API->>Backend: 6. POST /auth/scp/store_reply
|
|
|
+ Backend-->>API: 7. 返回结果
|
|
|
+ API-->>Thunk: 8. 返回 response
|
|
|
+ Thunk->>Redux: 9. 更新任务状态
|
|
|
+ end
|
|
|
+ Handler->>User: 10. 显示成功消息
|
|
|
+ end
|
|
|
+```
|
|
|
+
|
|
|
+### 3.2 数据流向
|
|
|
+
|
|
|
+```
|
|
|
+1. 用户选择任务
|
|
|
+ └─> handleRowClick (OutputList)
|
|
|
+ └─> dispatch(setSelectedIds([taskId]))
|
|
|
+ └─> Redux: sendJobSelection.selectedIds = [taskId]
|
|
|
+
|
|
|
+2. 用户点击重试
|
|
|
+ └─> handleRetry (OutputActionPanel)
|
|
|
+ └─> 读取 selectedIds
|
|
|
+ └─> 遍历并 dispatch(retrySendJobThunk(taskId))
|
|
|
+ └─> retrySendTask(taskId) API 调用
|
|
|
+ └─> POST /auth/scp/store_reply { task_id }
|
|
|
+ └─> 后端处理并返回结果
|
|
|
+ └─> Redux 更新任务状态
|
|
|
+ └─> 表格自动刷新
|
|
|
+```
|
|
|
+
|
|
|
+## 四、相关数据结构
|
|
|
+
|
|
|
+### 4.1 SendJob 实体
|
|
|
+```typescript
|
|
|
+interface SendJob {
|
|
|
+ task_id: string; // 任务唯一标识
|
|
|
+ patient_name: string; // 患者姓名
|
|
|
+ patient_id: string; // 患者 ID
|
|
|
+ priority: SendJobPriority; // 优先级: High/Medium/Low
|
|
|
+ status: SendJobStatus; // 状态: ARRIVED/SENDING/FAILED/SUCCESS
|
|
|
+ retry_count?: number; // 重试次数
|
|
|
+ destination: string; // 目标 PACS 节点
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 4.2 API 请求/响应
|
|
|
+```typescript
|
|
|
+// 请求参数
|
|
|
+interface RetrySendTaskRequest {
|
|
|
+ task_id: string;
|
|
|
+}
|
|
|
+
|
|
|
+// 响应数据
|
|
|
+interface RetrySendTaskResponse {
|
|
|
+ code: string; // 响应码:0x000000 表示成功
|
|
|
+ description: string; // 描述信息
|
|
|
+ solution: string; // 解决方案
|
|
|
+ data: StoreReplyData;
|
|
|
+}
|
|
|
+
|
|
|
+interface StoreReplyData {
|
|
|
+ '@type': string;
|
|
|
+ ok: boolean; // 是否成功
|
|
|
+ output: string; // 输出信息
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 4.3 Redux State
|
|
|
+```typescript
|
|
|
+// 选中状态
|
|
|
+interface SelectionState {
|
|
|
+ selectedIds: string[]; // 选中的 task_id 数组
|
|
|
+}
|
|
|
+
|
|
|
+// 任务列表状态
|
|
|
+interface EntitiesState<SendJob> {
|
|
|
+ data: SendJob[]; // 任务数组
|
|
|
+ total: number; // 总数
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## 五、功能特性
|
|
|
+
|
|
|
+### 5.1 用户体验优化
|
|
|
+
|
|
|
+1. **校验提示**
|
|
|
+ - 未选中任务时显示警告:"请先选择要重试的任务"
|
|
|
+
|
|
|
+2. **成功反馈**
|
|
|
+ - 显示具体重试的任务数量:"成功提交 N 个重试任务"
|
|
|
+
|
|
|
+3. **错误处理**
|
|
|
+ - API 调用失败时显示友好错误消息
|
|
|
+ - Console 输出详细错误信息便于调试
|
|
|
+
|
|
|
+4. **批量操作**
|
|
|
+ - 支持同时重试多个任务
|
|
|
+ - 使用 `Promise.all` 并行执行,提高效率
|
|
|
+
|
|
|
+### 5.2 状态自动更新
|
|
|
+
|
|
|
+重试成功后,Redux Store 会自动更新:
|
|
|
+- `status` 更新为 `'SENDING'`
|
|
|
+- `retry_count` 自动增加 1
|
|
|
+- 表格自动刷新显示最新状态
|
|
|
+
|
|
|
+### 5.3 错误容错
|
|
|
+
|
|
|
+- API 层捕获并抛出错误
|
|
|
+- Thunk 层处理 rejected 状态
|
|
|
+- 组件层显示用户友好的错误提示
|
|
|
+
|
|
|
+## 六、测试要点
|
|
|
+
|
|
|
+### 6.1 功能测试
|
|
|
+
|
|
|
+1. **正常流程**
|
|
|
+ - [ ] 选中单个任务,点击重试,验证成功
|
|
|
+ - [ ] 选中多个任务,点击重试,验证批量成功
|
|
|
+ - [ ] 验证重试后任务状态更新为 SENDING
|
|
|
+ - [ ] 验证 retry_count 正确增加
|
|
|
+
|
|
|
+2. **边界情况**
|
|
|
+ - [ ] 未选中任务时点击重试,显示警告
|
|
|
+ - [ ] 选中后取消选中,再点击重试,显示警告
|
|
|
+
|
|
|
+3. **错误处理**
|
|
|
+ - [ ] 模拟网络错误,验证错误提示
|
|
|
+ - [ ] 模拟后端返回错误码,验证错误处理
|
|
|
+
|
|
|
+### 6.2 集成测试
|
|
|
+
|
|
|
+1. **与表格的集成**
|
|
|
+ - [ ] 验证选中状态正确同步
|
|
|
+ - [ ] 验证表格数据更新后正确显示
|
|
|
+
|
|
|
+2. **与分页的集成**
|
|
|
+ - [ ] 跨页选中任务,验证重试功能
|
|
|
+
|
|
|
+## 七、API 接口说明
|
|
|
+
|
|
|
+### 7.1 重试接口
|
|
|
+
|
|
|
+**端点:** `POST /auth/scp/store_reply`
|
|
|
+
|
|
|
+**请求体:**
|
|
|
+```json
|
|
|
+{
|
|
|
+ "task_id": "0199cd46-82f0-76c5-b1d3-9399668a1a05"
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**响应示例(成功):**
|
|
|
+```json
|
|
|
+{
|
|
|
+ "code": "0x000000",
|
|
|
+ "description": "Success",
|
|
|
+ "solution": "",
|
|
|
+ "data": {
|
|
|
+ "@type": "StoreReply",
|
|
|
+ "ok": true,
|
|
|
+ "output": "Successfully retried task"
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**响应示例(失败):**
|
|
|
+```json
|
|
|
+{
|
|
|
+ "code": "0x000001",
|
|
|
+ "description": "Task not found",
|
|
|
+ "solution": "Please check the task ID",
|
|
|
+ "data": {
|
|
|
+ "@type": "StoreReply",
|
|
|
+ "ok": false,
|
|
|
+ "output": "Task does not exist"
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## 八、注意事项
|
|
|
+
|
|
|
+1. **参数变更**
|
|
|
+ - 原设计使用 `instance_uid`,实际实现改为使用 `task_id`
|
|
|
+ - 确保后端接口支持 `task_id` 参数
|
|
|
+
|
|
|
+2. **状态更新**
|
|
|
+ - 重试成功后状态设为 `SENDING`,而非 `SUCCESS`
|
|
|
+ - 实际成功与否需要后续状态轮询确认
|
|
|
+
|
|
|
+3. **并发控制**
|
|
|
+ - 当前实现使用 `Promise.all` 并行重试
|
|
|
+ - 如需控制并发数,可使用 `Promise.allSettled` 或限流
|
|
|
+
|
|
|
+4. **权限控制**
|
|
|
+ - 当前未做权限检查
|
|
|
+ - 如需要,应在按钮层或 API 层添加权限校验
|
|
|
+
|
|
|
+## 九、未来改进方向
|
|
|
+
|
|
|
+1. **加载状态**
|
|
|
+ - 添加按钮 loading 状态,防止重复点击
|
|
|
+ - 显示重试进度(如 "重试中... 2/5")
|
|
|
+
|
|
|
+2. **单独重试**
|
|
|
+ - 在表格行中添加单独的重试按钮
|
|
|
+ - 支持直接重试单个任务,无需选中
|
|
|
+
|
|
|
+3. **重试策略**
|
|
|
+ - 支持配置重试次数限制
|
|
|
+ - 支持指数退避重试
|
|
|
+
|
|
|
+4. **结果反馈**
|
|
|
+ - 显示详细的重试结果(成功 N 个,失败 M 个)
|
|
|
+ - 失败时显示具体原因
|
|
|
+
|
|
|
+## 十、总结
|
|
|
+
|
|
|
+本次实现完成了传输队列的重试发送任务功能,主要包括:
|
|
|
+
|
|
|
+1. ✅ 修改 API 层参数,从 `instance_uid` 改为 `task_id`
|
|
|
+2. ✅ 简化状态管理层 Thunk 参数
|
|
|
+3. ✅ 实现组件层重试逻辑和用户交互
|
|
|
+4. ✅ 支持批量重试多个任务
|
|
|
+5. ✅ 实现完善的错误处理和用户反馈
|
|
|
+6. ✅ 自动更新任务状态和重试计数
|
|
|
+
|
|
|
+该功能已完全集成到现有的传输队列管理系统中,遵循了项目的架构设计和编码规范。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+**实现日期:** 2025-10-11
|
|
|
+**实现人员:** Cline
|
|
|
+**文档版本:** 1.0
|