浏览代码

完善文档:初始化管道设计方案

dengdx 3 周之前
父节点
当前提交
28588c70d6
共有 1 个文件被更改,包括 564 次插入0 次删除
  1. 564 0
      docs/实现/初始化管道系统设计方案.md

+ 564 - 0
docs/实现/初始化管道系统设计方案.md

@@ -783,3 +783,567 @@ try {
 - ✅ **可观测性**:完整的状态追踪和监控
 
 这个初始化管道系统是**现代 React 应用的最佳实践**,特别适用于需要复杂初始化流程的企业级应用。
+
+---
+
+## 🔍 **旧的启动逻辑分析**
+
+以下是对现有程序从启动到进入登录页面的完整流程分析。
+
+### **程序启动流程概览**
+
+从程序开始运行到显示登录页面,经历了**两个主要层面**的初始化:
+
+#### **两层架构说明**
+
+程序的启动流程分为两个独立的层面:
+
+**第一层:环境层初始化(Environment Layer)**
+- **职责**:处理特定运行环境的底层初始化
+- **范围**:根据不同环境有不同的实现
+- **生命周期**:在 React 应用启动之前完成
+
+**第二层:React 应用层初始化(Application Layer)**
+- **职责**:处理应用业务逻辑的初始化
+- **范围**:所有环境共享同一套逻辑
+- **生命周期**:在环境层初始化完成后开始
+
+#### **不同环境的初始化差异**
+
+| 环境类型 | 环境层初始化 | 应用层初始化 |
+|---------|------------|------------|
+| **Electron** | ✅ 单实例检查<br>✅ 创建 BrowserWindow<br>✅ 加载 h5/index.html<br>✅ 设置 IPC 处理器<br>✅ 服务器连接检查 | ✅ 产品初始化<br>✅ 多语言加载<br>✅ 注释管理器初始化 |
+| **浏览器** | ❌ 无单实例检查<br>❌ 无窗口创建<br>❌ 无 IPC 处理器<br>❌ 无服务器连接检查 | ✅ 产品初始化<br>✅ 多语言加载<br>✅ 注释管理器初始化 |
+| **Cordova** | ✅ Cordova 插件初始化<br>✅ 设备就绪检查<br>✅ 服务器连接检查 | ✅ 产品初始化<br>✅ 多语言加载<br>✅ 注释管理器初始化 |
+
+#### **分层启动流程图**
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ 第一层:环境层初始化(Environment Layer)                    │
+├─────────────────────────────────────────────────────────────┤
+│                                                             │
+│  Electron 环境:                                             │
+│  ├─ 单实例检查(app.requestSingleInstanceLock)             │
+│  ├─ 等待应用就绪(app.whenReady)                           │
+│  ├─ 创建 BrowserWindow                                      │
+│  ├─ 设置 IPC 处理器                                         │
+│  └─ 加载 h5/index.html                                      │
+│                                                             │
+│  浏览器环境:                                                │
+│  └─ 直接加载 index.html(无需额外初始化)                   │
+│                                                             │
+│  Cordova 环境:                                              │
+│  ├─ Cordova 插件初始化                                      │
+│  ├─ 设备就绪检查(deviceready 事件)                        │
+│  └─ 加载应用主页面                                          │
+│                                                             │
+└─────────────────────────────────────────────────────────────┘
+                         ↓
+┌─────────────────────────────────────────────────────────────┐
+│ 第二层:React 应用层初始化(Application Layer)              │
+├─────────────────────────────────────────────────────────────┤
+│                                                             │
+│  所有环境共享:                                              │
+│  ├─ React App 渲染                                          │
+│  ├─ Redux Provider 初始化                                   │
+│  ├─ 【并发执行阶段】                                        │
+│  │   ├─ 【线程1】平台检测与服务器连接(app.tsx)             │
+│  │   │   ├─ 平台检测(platform.isBrowser/isElectron/isCordova) │
+│  │   │   ├─ 服务器连接检查(仅非浏览器环境)                 │
+│  │   │   └─ 如果连接失败 → 显示服务器配置对话框            │
+│  │   └─ 【线程2】Taro路由加载云同步页面                     │
+│  │       ├─ 加载 cloud_sync_expired 页面                    │
+│  │       ├─ 调用 getQuota() API                             │
+│  │       ├─ 检查 overdue 状态                               │
+│  │       ├─ 如果过期 → 显示提示并重试(最多10次)            │
+│  │       └─ 如果未过期 → 跳转到 pages/index/index          │
+│  ├─ 【阶段 2】应用状态初始化                                │
+│  │   ├─ 初始化产品状态(initializeProductState)             │
+│  │   ├─ 加载多语言资源(loadI18nMessages)                   │
+│  │   ├─ 初始化注释管理器(initializeAnnotationManager)      │
+│  │   └─ 标记应用就绪(isI18nReady = true)                   │
+│                                                             │
+└─────────────────────────────────────────────────────────────┘
+                         ↓
+┌─────────────────────────────────────────────────────────────┐
+│ 页面渲染层(Presentation Layer)                            │
+├─────────────────────────────────────────────────────────────┤
+│                                                             │
+│  ├─ 检查登录状态                                            │
+│  ├─ 未登录 → 显示登录页面                                   │
+│  ├─ 已登录但未初始化 → 显示初始化组件                       │
+│  └─ 已登录且已初始化 → 显示主应用界面                       │
+│                                                             │
+└─────────────────────────────────────────────────────────────┘
+```
+
+---
+
+### **详细阶段分析**
+
+从程序开始运行到显示登录页面,经历了以下几个主要阶段:
+
+#### **1. Electron 主进程启动** (main.js)
+
+**入口文件**:`main.js`
+
+**主要步骤**:
+- 应用启动时执行 `main.js`
+- 进行单实例检查(`app.requestSingleInstanceLock()`)
+  - 如果抢锁失败,立即退出应用
+  - 如果抢锁成功,监听后续启动事件
+- 等待应用就绪(`app.whenReady()`)
+- 调用 `createWindow()` 创建浏览器窗口
+  - 创建隐藏的无边框窗口
+  - 配置 webPreferences(禁用 nodeIntegration,启用 contextIsolation)
+  - 加载预加载脚本 `preload.js`
+  - 移除应用菜单栏
+  - 窗口最大化
+- 加载页面:`win.loadFile(join(process.cwd(), 'h5/index.html'))`
+- 窗口准备就绪后显示:`win.once('ready-to-show', () => win.show())`
+- 设置各种 IPC 处理器:
+  - 存储操作(storage-get, storage-set, storage-remove)
+  - 日志功能(write-log)
+  - 应用控制(exit-close, exit-shutdown)
+  - 打印功能(print-film, print-film-to-file, print-to-pdf)
+
+**关键代码**:
+```javascript
+function createWindow() {
+  win = new BrowserWindow({
+    show: false,
+    frame: false,
+    titleBarStyle: 'hidden',
+    webPreferences: {
+      nodeIntegration: false,
+      contextIsolation: true,
+      preload: join(__dirname, 'preload.js'),
+    },
+  });
+  
+  Menu.setApplicationMenu(null);
+  win.maximize();
+  win.loadFile(join(process.cwd(), 'h5/index.html'));
+  win.once('ready-to-show', () => win.show());
+}
+
+app.whenReady().then(createWindow);
+```
+
+---
+
+#### **2. React 应用初始化** (src/app.tsx)
+
+**入口组件**:`App` 组件
+
+**主要逻辑**:
+- 渲染 `App` 组件,包裹 Redux Provider
+- `AppContent` 组件开始执行初始化逻辑
+
+---
+
+#### **3. 云同步过期检查** (src/pages/security/cloud_sync_expired.tsx)
+
+**触发时机**:Taro 路由加载的第一个页面
+
+**配置位置**:在 `src/app.config.ts` 中配置为第一个页面
+```typescript
+export default defineAppConfig({
+  pages: [
+    'pages/security/cloud_sync_expired', // 第一个页面
+    'pages/index/index',
+  ],
+});
+```
+
+**检查流程**:
+1. **调用配额检查 API**
+   ```typescript
+   const response = await getQuota();
+   const isOverdue = response.data.overdue;
+   ```
+
+2. **判断过期状态**
+   - 如果 `overdue === false`:授权有效,跳转到主入口页面
+     ```typescript
+     Taro.redirectTo({ url: '/pages/index/index' });
+     ```
+   - 如果 `overdue === true`:显示过期提示
+
+3. **重试机制**
+   - 自动重试:最多重试 10 次,每次间隔 1 秒
+   - 手动重试:用户可以点击"手动重试"按钮
+   - 错误处理:API 调用失败也会触发重试
+
+**显示状态**:
+- 检查中:"正在检查授权状态..."
+- 过期:"请与云端同步以继续使用"
+- 有效:"授权有效,正在跳转..."
+
+**关键代码**:
+```typescript
+const checkoverdue = async (currentRetry = 0) => {
+  try {
+    const response = await getQuota();
+    setoverdueStatus(response.data.overdue);
+    
+    if (!response.data.overdue) {
+      // 未过期,跳转到主页面
+      Taro.redirectTo({ url: '/pages/index/index' });
+    } else {
+      // 过期,需要重试
+      if (currentRetry < maxRetries) {
+        setTimeout(() => checkoverdue(currentRetry + 1), 1000);
+      }
+    }
+  } catch (error) {
+    // 错误处理和重试逻辑
+  }
+};
+```
+
+**重要性**:
+- 这是应用启动后的第一道检查
+- 只有通过云同步检查,才能进入后续的初始化流程
+- 保证了应用的授权合法性
+
+**环境检测与初始化**:
+- **浏览器环境**:
+  - 检测到 `platform.isBrowser` 为 true
+  - 跳过服务器连接检查
+  - 直接调用 `initializeApp()`
+  
+- **Electron/Cordova 环境**:
+  - 执行 `dispatch(checkServerConnection())`
+  - 根据检查结果决定:
+    - 如果 `result.needsConfig` 为 true:显示服务器配置对话框
+    - 如果连接正常:继续调用 `initializeApp()`
+  - 如果检查失败:显示配置对话框
+
+**关键代码**:
+```typescript
+useEffect(() => {
+  // 浏览器环境不需要服务器连接检查,直接初始化应用
+  if (platform.isBrowser) {
+    console.log('浏览器环境,跳过服务器连接检查,直接初始化应用');
+    initializeApp();
+    return;
+  }
+
+  // Electron/Cordova 环境:检查服务器连接
+  dispatch(checkServerConnection())
+    .unwrap()
+    .then((result) => {
+      setConnectionChecked(true);
+      if (result.needsConfig) {
+        console.log('检测到需要服务器配置,显示配置对话框');
+        setShowConfigModal(true);
+      } else {
+        console.log('服务器连接正常,开始应用初始化');
+        return initializeApp();
+      }
+    })
+    .catch((error) => {
+      console.error('连接检查失败:', error);
+      setConnectionChecked(true);
+      setShowConfigModal(true);
+    });
+}, [dispatch]);
+```
+
+#### **4. 应用初始化流程** (initializeApp 函数)
+
+**执行步骤**:
+
+1. **初始化产品状态**
+   ```typescript
+   const productState = await dispatch(initializeProductState()).unwrap();
+   ```
+   - 获取产品配置信息
+   - 包含语言设置等配置
+
+2. **加载国际化资源**
+   ```typescript
+   const languageCode = productState.language;
+   await dispatch(loadI18nMessages(languageCode)).unwrap();
+   ```
+   - 根据产品配置的语言代码加载对应的多语言文件
+   - 加载完成后设置到 Redux store
+
+3. **初始化注释管理器**
+   ```typescript
+   await initializeAnnotationManager();
+   ```
+   - 初始化图像注释相关功能
+
+4. **标记初始化完成**
+   ```typescript
+   setIsI18nReady(true);
+   ```
+   - 设置国际化就绪状态
+   - 允许渲染后续界面
+
+**错误处理**:
+- 如果初始化失败,显示服务器配置对话框
+- 允许用户重新配置后重试
+
+#### **5. 页面路由加载** (src/pages/index/index.tsx)
+
+**路由配置** (src/app.config.ts):
+```typescript
+export default defineAppConfig({
+  pages: [
+    'pages/security/cloud_sync_expired',
+    'pages/index/index',
+  ],
+  // ...
+});
+```
+
+**入口页面渲染逻辑**:
+
+`AppContent` 组件根据状态决定渲染内容:
+
+```typescript
+const AppContent: React.FC = () => {
+  const [initialized, setInitialized] = React.useState(false);
+  const userInfo = useSelector((state: RootState) => state.userInfo);
+  const loggedIn = isLoggedIn(userInfo);
+
+  // 1. 未登录 → 显示登录页面
+  if (!loggedIn) {
+    return <Login />;
+  }
+
+  // 2. 已登录但未初始化 → 显示初始化组件
+  if (!initialized) {
+    return <AppInitializer onInitialized={() => setInitialized(true)} />;
+  }
+
+  // 3. 已登录且已初始化 → 显示主布局
+  return (
+    <Router>
+      <BasicLayout children={undefined}></BasicLayout>
+    </Router>
+  );
+};
+```
+
+**状态判断逻辑**:
+- **第一优先级**:检查用户是否登录(`isLoggedIn(userInfo)`)
+- **第二优先级**:检查应用是否初始化(`initialized` 状态)
+- **第三优先级**:渲染主应用布局
+
+**token 过期处理**:
+```typescript
+useEffect(() => {
+  const handleTokenExpired = () => {
+    dispatch(clearUserInfo());
+    message.error('Your session has expired. Please log in again.');
+  };
+
+  emitter.on('tokenExpired', handleTokenExpired);
+  return () => {
+    emitter.off('tokenExpired', handleTokenExpired);
+  };
+}, [dispatch]);
+```
+
+#### **6. 登录页面显示** (src/pages/security/Login.tsx)
+
+**显示条件**:
+```typescript
+// 急诊模式下不显示登录页面
+if (systemMode === SystemMode.Emergency) {
+  return null;
+}
+
+// 正常模式但已登录,不显示登录页面
+if (systemMode === SystemMode.Normal && !!userInfo && userInfo.uid !== 0) {
+  return null;
+}
+```
+
+**登录页面功能**:
+
+1. **用户名密码登录**
+   - 输入用户名和密码
+   - 调用登录 API:`loginApi(username, password)`
+   - 登录成功后:
+     - 保存用户信息到 Redux:`dispatch(setUserInfo(userInfo))`
+     - 设置系统模式为正常:`dispatch(setSystemMode(SystemMode.Normal))`
+     - 显示成功消息
+
+2. **急诊模式**
+   - 点击"急诊"按钮
+   - 执行急诊操作:`handleEmergencyOperation()`
+   - 进入急诊工作流
+
+**自动聚焦**:
+```typescript
+useEffect(() => {
+  if (usernameInputRef.current) {
+    usernameInputRef.current.focus();
+  }
+}, []);
+```
+
+### **完整启动时序图**
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ 1. Electron 启动                                             │
+│    └─ main.js 执行                                           │
+│       ├─ 单实例检查                                          │
+│       ├─ 等待应用就绪 (app.whenReady)                        │
+│       ├─ 创建 BrowserWindow                                  │
+│       └─ 加载 h5/index.html                                  │
+└─────────────────────────────────────────────────────────────┘
+                         ↓
+┌─────────────────────────────────────────────────────────────┐
+│ 2. React App 渲染 (app.tsx)                                  │
+│    ├─ Redux Provider 初始化                                  │
+│    └─ AppContent 组件渲染                                    │
+└─────────────────────────────────────────────────────────────┘
+                         ↓
+┌─────────────────────────────────────────────────────────────┐
+│ 3. 【并发执行】平台检测 + 页面路由加载                       │
+│                                                             │
+│    【线程1: 平台检测与服务器连接】                           │
+│    ├─ 平台检测(platform.isBrowser/isElectron/isCordova)     │
+│    ├─ 服务器连接检查(仅非浏览器环境,异步API调用)            │
+│    └─ 如果连接失败 → 显示服务器配置对话框(IP输入框)         │
+│                                                             │
+│    【线程2: Taro路由加载云同步页面】                         │
+│    ├─ 加载 cloud_sync_expired 页面                           │
+│    ├─ 调用 getQuota() API                                    │
+│    ├─ 检查 overdue 状态                                      │
+│    ├─ 如果过期 → 显示提示并支持重试(重试提示)               │
+│    └─ 如果未过期 → 跳转到 pages/index/index                 │
+│                                                             │
+│    【结果】两个UI可能同时显示:                               │
+│    ├─ 服务器配置对话框(IP输入框)                            │
+│    └─ 云同步过期重试提示                                     │
+└─────────────────────────────────────────────────────────────┘
+                         ↓
+┌─────────────────────────────────────────────────────────────┐
+│ 4. 进入主页面后的应用初始化                                  │
+│    ├─ initializeProductState() - 获取产品配置               │
+│    ├─ loadI18nMessages() - 加载多语言资源                   │
+│    ├─ initializeAnnotationManager() - 初始化注释管理器      │
+│    └─ setIsI18nReady(true) - 标记初始化完成                 │
+└─────────────────────────────────────────────────────────────┘
+                         ↓
+┌─────────────────────────────────────────────────────────────┐
+│ 5. 主页面状态判断 (pages/index/index.tsx)                    │
+│    └─ AppContent 状态判断                                    │
+│       ├─ !loggedIn → 渲染 <Login />                          │
+│       ├─ !initialized → 渲染 <AppInitializer />              │
+│       └─ 已登录且已初始化 → 渲染 <BasicLayout />             │
+└─────────────────────────────────────────────────────────────┘
+                         ↓
+┌─────────────────────────────────────────────────────────────┐
+│ 6. 显示登录页面 (Login.tsx)                                  │
+│    ├─ 检查系统模式 (不在急诊模式)                           │
+│    ├─ 检查登录状态 (未登录)                                 │
+│    └─ 渲染登录表单                                           │
+│       ├─ 用户名输入框 (自动聚焦)                            │
+│       ├─ 密码输入框                                          │
+│       ├─ 登录按钮                                            │
+│       └─ 急诊按钮                                            │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### **关键状态判断**
+
+登录页面是否显示取决于以下条件:
+
+| 条件 | 结果 |
+|------|------|
+| `systemMode === Emergency` | 不显示登录页面 |
+| `systemMode === Normal && isLoggedIn(userInfo)` | 不显示登录页面 |
+| `systemMode === Normal && !isLoggedIn(userInfo)` | **显示登录页面** |
+
+### **数据流向**
+
+```mermaid
+flowchart TD
+    Start([应用启动]) --> ElectronMain[Electron 主进程]
+    ElectronMain --> LoadHTML[加载 h5/index.html]
+    LoadHTML --> ReactApp[React App 初始化]
+    
+    ReactApp --> PlatformCheck{平台检测}
+    
+    PlatformCheck -->|浏览器| DirectInit[直接初始化应用]
+    PlatformCheck -->|Electron/Cordova| ServerCheck[检查服务器连接]
+    
+    ServerCheck --> ServerOK{连接正常?}
+    ServerOK -->|否| ShowConfig[显示配置对话框]
+    ShowConfig --> UserConfig[用户输入配置]
+    UserConfig --> ServerCheck
+    ServerOK -->|是| InitApp[initializeApp]
+    
+    DirectInit --> InitApp
+    
+    InitApp --> ProductInit[初始化产品状态]
+    ProductInit --> I18nLoad[加载多语言资源]
+    I18nLoad --> AnnotationInit[初始化注释管理器]
+    AnnotationInit --> Ready[设置 isI18nReady = true]
+    
+    Ready --> LoadPage[加载 pages/index/index]
+    
+    LoadPage --> LoginCheck{用户已登录?}
+    LoginCheck -->|否| ShowLogin[显示登录页面]
+    LoginCheck -->|是| InitCheck{应用已初始化?}
+    
+    InitCheck -->|否| ShowInitializer[显示初始化组件]
+    InitCheck -->|是| ShowMainUI[显示主应用界面]
+    
+    ShowLogin --> LoginForm[登录表单]
+    LoginForm --> LoginAction[用户登录]
+    LoginAction --> SaveUserInfo[保存用户信息]
+    SaveUserInfo --> ShowMainUI
+```
+
+### **涉及的关键文件**
+
+| 文件路径 | 作用 | 关键函数/组件 |
+|---------|------|--------------|
+| `src/app.config.ts` | Taro 页面路由配置 | 页面顺序配置 |
+| `src/pages/security/cloud_sync_expired.tsx` | 云同步过期检查页面 | `checkoverdue()`, `getQuota()` |
+| `main.js` | Electron 主进程入口 | `createWindow()`, `app.whenReady()` |
+| `src/app.tsx` | React 应用入口 | `App`, `AppContent`, `initializeApp()` |
+| `src/pages/index/index.tsx` | 应用主入口页面 | `AppContent`, 登录状态判断 |
+| `src/pages/security/Login.tsx` | 登录页面 | `Login`, `handleFinish()`, `handleEmergencyClick()` |
+| `src/states/user_info.ts` | 用户信息状态管理 | `setUserInfo`, `isLoggedIn` |
+| `src/states/productSlice.ts` | 产品状态管理 | `initializeProductState` |
+| `src/states/i18nSlice.ts` | 国际化状态管理 | `loadI18nMessages` |
+| `src/features/serverConfig/` | 服务器配置功能 | `checkServerConnection`, `ServerConfigModal` |
+
+### **现有启动流程的特点**
+
+**优点**:
+- ✅ 支持多平台(浏览器、Electron、Cordova)
+- ✅ 有基本的错误处理和配置引导
+- ✅ 国际化支持完善
+- ✅ 状态管理清晰(Redux)
+
+**存在的问题**:
+- ❌ 初始化逻辑分散在多个文件中,不易维护
+- ❌ 缺乏统一的进度显示和状态追踪
+- ❌ 错误处理不够精细,难以针对不同错误提供不同的恢复策略
+- ❌ 初始化步骤之间的依赖关系不够清晰
+- ❌ 难以扩展新的初始化步骤
+- ❌ 调试困难,缺乏详细的日志和状态追踪
+
+**改进方向**:
+- 引入初始化管道系统,将初始化流程标准化
+- 提供统一的进度显示和状态管理
+- 实现更精细的错误处理和恢复机制
+- 使用状态机管理初始化流程
+- 支持节点级别的重试和用户交互
+
+---
+
+**📝 备注**:以上分析基于当前代码库的实际情况,为设计新的初始化管道系统提供参考。