|
@@ -0,0 +1,469 @@
|
|
|
+# 急诊流程 - Token 获取与传递机制
|
|
|
+
|
|
|
+## 概述
|
|
|
+
|
|
|
+本文档详细说明了系统中急诊模式的完整逻辑,包括 guest token 的获取、存储、传递机制以及急诊操作的完整流程。
|
|
|
+
|
|
|
+## 1. Token 的来源(初始化阶段)
|
|
|
+
|
|
|
+### 相关文件
|
|
|
+
|
|
|
+- `src/API/softwareInfo.ts` - API 调用
|
|
|
+- `src/states/productSlice.ts` - Redux 状态管理
|
|
|
+
|
|
|
+### 获取流程
|
|
|
+
|
|
|
+在系统启动时,通过 `initializeProductState` thunk 自动获取软件信息:
|
|
|
+
|
|
|
+```typescript
|
|
|
+// API 调用
|
|
|
+GET / pub / software_info;
|
|
|
+
|
|
|
+// 返回数据结构
|
|
|
+interface SoftwareInfo {
|
|
|
+ guest: string; // 用于急诊访问数据的 token
|
|
|
+ FPD: string;
|
|
|
+ GEN: string;
|
|
|
+ language: string[];
|
|
|
+ product: string;
|
|
|
+ sn: string;
|
|
|
+ server: Record<string, any>;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 存储到 Redux
|
|
|
+
|
|
|
+```typescript
|
|
|
+// productSlice.ts
|
|
|
+export const initializeProductState = createAsyncThunk(
|
|
|
+ 'product/initializeProductState',
|
|
|
+ async () => {
|
|
|
+ const softwareInfo = await fetchSoftwareInfo();
|
|
|
+ return {
|
|
|
+ productName: softwareInfo.product as 'DROS' | 'VETDROS',
|
|
|
+ language: softwareInfo.language[0],
|
|
|
+ source: 'Browser' as const,
|
|
|
+ guest: softwareInfo.guest, // 存储 guest token
|
|
|
+ };
|
|
|
+ }
|
|
|
+);
|
|
|
+```
|
|
|
+
|
|
|
+### 关键点
|
|
|
+
|
|
|
+- ✅ `guest` 字段本质上就是急诊模式下使用的 token
|
|
|
+- ✅ 在系统初始化时就已经从后端获取并保存
|
|
|
+- ✅ 存储在 Redux 的 `productSlice` 中,路径:`state.product.guest`
|
|
|
+- ✅ 无需用户登录即可获得
|
|
|
+
|
|
|
+## 2. Token 的传递机制
|
|
|
+
|
|
|
+### 相关文件
|
|
|
+
|
|
|
+- `src/API/interceptor.ts` - Axios 拦截器
|
|
|
+
|
|
|
+### 核心逻辑
|
|
|
+
|
|
|
+在每个 API 请求发出前,axios 请求拦截器会自动根据当前系统模式选择合适的 token:
|
|
|
+
|
|
|
+```typescript
|
|
|
+axiosInstance.interceptors.request.use((config) => {
|
|
|
+ const state = store.getState();
|
|
|
+
|
|
|
+ // 根据系统模式选择 token
|
|
|
+ const token =
|
|
|
+ state.systemMode.mode === SystemMode.Emergency
|
|
|
+ ? state.product.guest // 急诊模式使用 guest token
|
|
|
+ : state.userInfo.token; // 正常模式使用登录 token
|
|
|
+
|
|
|
+ // 设置请求头
|
|
|
+ config.headers.Authorization = `Bearer ${token}`;
|
|
|
+ config.headers.Language = language;
|
|
|
+ config.headers.Product = productName;
|
|
|
+ config.headers.Source = source;
|
|
|
+
|
|
|
+ return config;
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+### 判断逻辑
|
|
|
+
|
|
|
+| 系统模式 | Token 来源 | 说明 |
|
|
|
+| ----------- | ---------------------- | ------------------------------------ |
|
|
|
+| `Emergency` | `state.product.guest` | 急诊模式,使用预先获取的 guest token |
|
|
|
+| `Normal` | `state.userInfo.token` | 正常模式,使用用户登录后的 token |
|
|
|
+| `Unknown` | `state.userInfo.token` | 未知状态,默认使用登录 token |
|
|
|
+
|
|
|
+### 优势
|
|
|
+
|
|
|
+- ✅ **透明化**:业务代码无需关心 token 的选择,由拦截器统一处理
|
|
|
+- ✅ **自动化**:根据系统模式自动切换 token
|
|
|
+- ✅ **集中管理**:所有 API 请求的认证逻辑集中在一处
|
|
|
+- ✅ **易维护**:修改认证逻辑只需修改拦截器
|
|
|
+
|
|
|
+## 3. 急诊操作的完整流程
|
|
|
+
|
|
|
+### 相关文件
|
|
|
+
|
|
|
+- `src/domain/patient/handleEmergencyOperation.ts` - 急诊操作主流程
|
|
|
+- `src/domain/patient/registrationGenerator.ts` - 生成注册信息
|
|
|
+- `src/API/patient/workActions.ts` - 注册 API
|
|
|
+- `src/states/systemModeSlice.ts` - 系统模式管理
|
|
|
+
|
|
|
+### 流程图
|
|
|
+
|
|
|
+```
|
|
|
+开始
|
|
|
+ ↓
|
|
|
+1. 设置系统模式为 Emergency
|
|
|
+ ↓
|
|
|
+2. 生成急诊注册信息
|
|
|
+ ↓
|
|
|
+3. 调用注册 API (自动使用 guest token)
|
|
|
+ ↓
|
|
|
+4. 保存注册结果到缓存
|
|
|
+ ↓
|
|
|
+5. 切换业务流程到检查页面
|
|
|
+ ↓
|
|
|
+结束
|
|
|
+```
|
|
|
+
|
|
|
+### 详细代码流程
|
|
|
+
|
|
|
+```typescript
|
|
|
+const handleEmergencyOperation = async () => {
|
|
|
+ const dispatch = store.dispatch;
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 步骤 1: 设置系统模式为急诊
|
|
|
+ dispatch(setSystemMode(SystemMode.Emergency));
|
|
|
+
|
|
|
+ // 步骤 2: 生成注册信息
|
|
|
+ const registrationInfo = generateRegistrationInfo();
|
|
|
+ // 返回的数据包含 study_type: 'Emergency'
|
|
|
+
|
|
|
+ // 步骤 3: 注册急诊工作
|
|
|
+ // 此时 API 请求会自动使用 guest token(通过拦截器)
|
|
|
+ const registrationResult = await registerWork(registrationInfo);
|
|
|
+
|
|
|
+ // 步骤 4: 保存结果到缓存
|
|
|
+ const task = mapToTask(registrationResult.data);
|
|
|
+ dispatch(addWork(task));
|
|
|
+
|
|
|
+ // 步骤 5: 切换到检查流程
|
|
|
+ dispatch(setBusinessFlow('exam'));
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error in handleEmergencyOperation:', error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+### 注册信息生成
|
|
|
+
|
|
|
+```typescript
|
|
|
+// registrationGenerator.ts
|
|
|
+function generateRegistrationInfo() {
|
|
|
+ return {
|
|
|
+ study_type: 'Emergency', // 标记为急诊类型
|
|
|
+ accession_number: maxNumber,
|
|
|
+ patient_id: `EMERGENCY_${timestamp}`,
|
|
|
+ patient_name: `急诊患者_${number}`,
|
|
|
+ // ... 其他字段
|
|
|
+ };
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## 4. 系统模式状态管理
|
|
|
+
|
|
|
+### 相关文件
|
|
|
+
|
|
|
+- `src/states/systemModeSlice.ts`
|
|
|
+
|
|
|
+### 模式定义
|
|
|
+
|
|
|
+```typescript
|
|
|
+export const SystemMode = {
|
|
|
+ Unknown: 'Unknown', // 未知状态
|
|
|
+ Emergency: 'Emergency', // 急诊模式
|
|
|
+ Normal: 'Normal', // 正常模式
|
|
|
+};
|
|
|
+
|
|
|
+interface SystemModeState {
|
|
|
+ mode: SystemMode;
|
|
|
+}
|
|
|
+
|
|
|
+const initialState: SystemModeState = {
|
|
|
+ mode: SystemMode.Unknown,
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+### 状态转换
|
|
|
+
|
|
|
+| 从状态 | 到状态 | 触发条件 |
|
|
|
+| ----------- | ----------- | ---------------------- |
|
|
|
+| `Unknown` | `Emergency` | 执行急诊操作 |
|
|
|
+| `Unknown` | `Normal` | 用户正常登录 |
|
|
|
+| `Emergency` | `Normal` | 急诊流程结束(待实现) |
|
|
|
+| `Normal` | `Emergency` | 切换到急诊模式 |
|
|
|
+
|
|
|
+### 使用方式
|
|
|
+
|
|
|
+```typescript
|
|
|
+// 设置为急诊模式
|
|
|
+dispatch(setSystemMode(SystemMode.Emergency));
|
|
|
+
|
|
|
+// 设置为正常模式
|
|
|
+dispatch(setSystemMode(SystemMode.Normal));
|
|
|
+
|
|
|
+// 获取当前模式
|
|
|
+const currentMode = state.systemMode.mode;
|
|
|
+```
|
|
|
+
|
|
|
+## 5. 核心设计思想
|
|
|
+
|
|
|
+### 5.1 预先获取 Token
|
|
|
+
|
|
|
+- **设计理念**:guest token 在系统初始化时就获取,无需等待用户登录
|
|
|
+- **优势**:急诊场景下可以立即使用,无需认证流程
|
|
|
+- **实现**:通过 `initializeProductState` thunk 在应用启动时自动获取
|
|
|
+
|
|
|
+### 5.2 模式驱动
|
|
|
+
|
|
|
+- **设计理念**:通过系统模式(Emergency/Normal)自动切换使用不同的 token
|
|
|
+- **优势**:业务逻辑与认证逻辑解耦
|
|
|
+- **实现**:在 axios 拦截器中根据 `systemMode.mode` 选择 token
|
|
|
+
|
|
|
+### 5.3 透明传递
|
|
|
+
|
|
|
+- **设计理念**:业务代码不需要关心 token 的选择
|
|
|
+- **优势**:降低代码复杂度,减少出错可能
|
|
|
+- **实现**:由 axios 拦截器统一处理
|
|
|
+
|
|
|
+### 5.4 急诊快速启动
|
|
|
+
|
|
|
+- **设计理念**:无需登录认证流程,直接使用预设的 guest token
|
|
|
+- **优势**:紧急情况下快速响应
|
|
|
+- **实现**:一键触发急诊流程,自动完成注册和进入检查页面
|
|
|
+
|
|
|
+## 6. API 接口说明
|
|
|
+
|
|
|
+### 6.1 获取软件信息
|
|
|
+
|
|
|
+```http
|
|
|
+GET /pub/software_info
|
|
|
+```
|
|
|
+
|
|
|
+**响应示例:**
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "code": "0x000000",
|
|
|
+ "data": {
|
|
|
+ "guest": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
|
+ "product": "DROS",
|
|
|
+ "language": ["en", "zh"],
|
|
|
+ "FPD": "Simulator",
|
|
|
+ "GEN": "Simulator",
|
|
|
+ "sn": "2edbc382-044adc78-95bed11b-51c9328a"
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 6.2 注册急诊工作
|
|
|
+
|
|
|
+```http
|
|
|
+POST /dr/api/v1/auth/study
|
|
|
+Authorization: Bearer {guest_token}
|
|
|
+```
|
|
|
+
|
|
|
+**请求体:**
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "study_type": "Emergency",
|
|
|
+ "accession_number": "ACC0012345",
|
|
|
+ "patient_id": "EMERGENCY_20250625143339",
|
|
|
+ "patient_name": "急诊患者_001"
|
|
|
+ // ... 其他患者信息
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## 7. 关键文件总结
|
|
|
+
|
|
|
+| 文件路径 | 作用 | 关键功能 |
|
|
|
+| ------------------------------------------------ | -------------- | ----------------------------------------------- |
|
|
|
+| `src/API/softwareInfo.ts` | 获取软件信息 | 调用 `/pub/software_info` API,获取 guest token |
|
|
|
+| `src/states/productSlice.ts` | 产品状态管理 | 存储 guest token 到 Redux |
|
|
|
+| `src/states/systemModeSlice.ts` | 系统模式管理 | 管理 Emergency/Normal 模式切换 |
|
|
|
+| `src/API/interceptor.ts` | API 拦截器 | 根据系统模式自动选择 token 并添加到请求头 |
|
|
|
+| `src/domain/patient/handleEmergencyOperation.ts` | 急诊操作主流程 | 执行完整的急诊注册流程 |
|
|
|
+| `src/domain/patient/registrationGenerator.ts` | 注册信息生成 | 生成急诊患者的注册信息 |
|
|
|
+| `src/API/patient/workActions.ts` | 工作注册 API | 调用后端注册接口 |
|
|
|
+
|
|
|
+## 8. 使用示例
|
|
|
+
|
|
|
+### 8.1 触发急诊流程
|
|
|
+
|
|
|
+```typescript
|
|
|
+import handleEmergencyOperation from '@/domain/patient/handleEmergencyOperation';
|
|
|
+
|
|
|
+// 在某个组件或页面中触发急诊
|
|
|
+const handleEmergencyClick = async () => {
|
|
|
+ try {
|
|
|
+ await handleEmergencyOperation();
|
|
|
+ // 成功后会自动跳转到检查页面
|
|
|
+ } catch (error) {
|
|
|
+ console.error('急诊操作失败:', error);
|
|
|
+ message.error('急诊操作失败,请重试');
|
|
|
+ }
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+### 8.2 检查当前系统模式
|
|
|
+
|
|
|
+```typescript
|
|
|
+import { useSelector } from 'react-redux';
|
|
|
+import { SystemMode } from '@/states/systemModeSlice';
|
|
|
+
|
|
|
+const MyComponent = () => {
|
|
|
+ const currentMode = useSelector(state => state.systemMode.mode);
|
|
|
+
|
|
|
+ const isEmergencyMode = currentMode === SystemMode.Emergency;
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div>
|
|
|
+ {isEmergencyMode ? '急诊模式' : '正常模式'}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+### 8.3 手动切换系统模式
|
|
|
+
|
|
|
+```typescript
|
|
|
+import { useDispatch } from 'react-redux';
|
|
|
+import { setSystemMode, SystemMode } from '@/states/systemModeSlice';
|
|
|
+
|
|
|
+const MyComponent = () => {
|
|
|
+ const dispatch = useDispatch();
|
|
|
+
|
|
|
+ const switchToNormal = () => {
|
|
|
+ dispatch(setSystemMode(SystemMode.Normal));
|
|
|
+ };
|
|
|
+
|
|
|
+ const switchToEmergency = () => {
|
|
|
+ dispatch(setSystemMode(SystemMode.Emergency));
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ <button onClick={switchToNormal}>切换到正常模式</button>
|
|
|
+ <button onClick={switchToEmergency}>切换到急诊模式</button>
|
|
|
+ </>
|
|
|
+ );
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+## 9. 注意事项
|
|
|
+
|
|
|
+### 9.1 Token 有效期
|
|
|
+
|
|
|
+- guest token 有过期时间(expire 字段)
|
|
|
+- 建议在应用中监控 token 过期状态
|
|
|
+- 过期后需要重新调用 `/pub/software_info` 获取新的 guest token
|
|
|
+
|
|
|
+### 9.2 系统模式切换
|
|
|
+
|
|
|
+- 当前急诊流程结束后,系统模式的切换逻辑尚未完全实现(代码中被注释)
|
|
|
+- 建议在急诊流程完成后显式切换回 Normal 模式
|
|
|
+
|
|
|
+```typescript
|
|
|
+// 在 handleEmergencyOperation 的 finally 中
|
|
|
+finally {
|
|
|
+ dispatch(setSystemMode(SystemMode.Normal));
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 9.3 错误处理
|
|
|
+
|
|
|
+- 所有 API 调用都应有适当的错误处理
|
|
|
+- 急诊流程失败时应给用户明确的提示
|
|
|
+- 考虑添加重试机制
|
|
|
+
|
|
|
+### 9.4 安全性
|
|
|
+
|
|
|
+- guest token 的权限应该受到限制,只能访问急诊相关的功能
|
|
|
+- 后端应验证请求的 study_type 与使用的 token 类型是否匹配
|
|
|
+- 建议添加访问日志记录急诊操作
|
|
|
+
|
|
|
+## 10. 未来改进建议
|
|
|
+
|
|
|
+### 10.1 Token 刷新机制
|
|
|
+
|
|
|
+实现 guest token 的自动刷新:
|
|
|
+
|
|
|
+```typescript
|
|
|
+// 在拦截器中检查 token 是否即将过期
|
|
|
+if (isTokenExpiringSoon(state.product.guest)) {
|
|
|
+ await refreshGuestToken();
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 10.2 急诊流程状态跟踪
|
|
|
+
|
|
|
+添加更详细的急诊流程状态:
|
|
|
+
|
|
|
+```typescript
|
|
|
+enum EmergencyFlowStatus {
|
|
|
+ NotStarted = 'NotStarted',
|
|
|
+ Generating = 'Generating',
|
|
|
+ Registering = 'Registering',
|
|
|
+ Completed = 'Completed',
|
|
|
+ Failed = 'Failed',
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 10.3 急诊模式自动退出
|
|
|
+
|
|
|
+实现急诊流程完成后自动退出急诊模式:
|
|
|
+
|
|
|
+```typescript
|
|
|
+const handleEmergencyOperation = async () => {
|
|
|
+ try {
|
|
|
+ dispatch(setSystemMode(SystemMode.Emergency));
|
|
|
+ // ... 执行急诊流程
|
|
|
+ dispatch(setBusinessFlow('exam'));
|
|
|
+ } finally {
|
|
|
+ // 检查完成后自动退出急诊模式
|
|
|
+ dispatch(setSystemMode(SystemMode.Normal));
|
|
|
+ }
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+### 10.4 审计日志
|
|
|
+
|
|
|
+记录所有急诊操作以便审计:
|
|
|
+
|
|
|
+```typescript
|
|
|
+const logEmergencyOperation = (operation: string, data: any) => {
|
|
|
+ console.log('[EMERGENCY]', {
|
|
|
+ timestamp: new Date().toISOString(),
|
|
|
+ operation,
|
|
|
+ data,
|
|
|
+ mode: store.getState().systemMode.mode,
|
|
|
+ });
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+## 11. 总结
|
|
|
+
|
|
|
+急诊流程的设计充分体现了以下特点:
|
|
|
+
|
|
|
+1. **快速响应**:通过预先获取 guest token,无需登录即可快速启动急诊流程
|
|
|
+2. **自动化**:系统模式驱动的 token 选择机制,业务代码无需关心认证细节
|
|
|
+3. **集中管理**:所有认证逻辑集中在 axios 拦截器中,便于维护
|
|
|
+4. **清晰分层**:状态管理、API 调用、业务逻辑分层明确
|
|
|
+5. **易于扩展**:模块化设计便于未来功能扩展
|
|
|
+
|
|
|
+这种设计使得急诊功能既能快速响应紧急情况,又保持了代码的可维护性和可扩展性。
|